@sasjs/adapter 3.2.1 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,4 +2,8 @@
2
2
  * Converts the given JSON object array to a CSV string.
3
3
  * @param data - the array of JSON objects to convert.
4
4
  */
5
- export declare const convertToCSV: (data: any) => string;
5
+ export declare const convertToCSV: (data: any, sasFormats?: {
6
+ formats: {
7
+ [key: string]: string;
8
+ };
9
+ } | undefined) => string;
package/package.json CHANGED
@@ -40,45 +40,45 @@
40
40
  },
41
41
  "license": "ISC",
42
42
  "devDependencies": {
43
- "@types/axios": "^0.14.0",
44
- "@types/express": "^4.17.13",
45
- "@types/form-data": "^2.5.0",
46
- "@types/jest": "^27.0.2",
47
- "@types/mime": "^2.0.3",
48
- "@types/pem": "^1.9.6",
49
- "@types/tough-cookie": "^4.0.1",
50
- "copyfiles": "^2.4.1",
51
- "cp": "^0.2.0",
52
- "dotenv": "^10.0.0",
53
- "express": "^4.17.1",
54
- "jest": "^27.2.0",
55
- "jest-extended": "^0.11.5",
56
- "node-polyfill-webpack-plugin": "^1.1.4",
57
- "path": "^0.12.7",
58
- "pem": "^1.14.4",
59
- "process": "^0.11.10",
60
- "rimraf": "^3.0.2",
61
- "semantic-release": "^18.0.0",
62
- "terser-webpack-plugin": "^5.2.4",
63
- "ts-jest": "^27.0.3",
64
- "ts-loader": "^9.2.6",
65
- "tslint": "^6.1.3",
66
- "tslint-config-prettier": "^1.18.0",
43
+ "@types/axios": "0.14.0",
44
+ "@types/express": "4.17.13",
45
+ "@types/form-data": "2.5.0",
46
+ "@types/jest": "27.0.2",
47
+ "@types/mime": "2.0.3",
48
+ "@types/pem": "1.9.6",
49
+ "@types/tough-cookie": "4.0.1",
50
+ "copyfiles": "2.4.1",
51
+ "cp": "0.2.0",
52
+ "dotenv": "10.0.0",
53
+ "express": "4.17.1",
54
+ "jest": "27.2.0",
55
+ "jest-extended": "0.11.5",
56
+ "node-polyfill-webpack-plugin": "1.1.4",
57
+ "path": "0.12.7",
58
+ "pem": "1.14.4",
59
+ "process": "0.11.10",
60
+ "rimraf": "3.0.2",
61
+ "semantic-release": "18.0.0",
62
+ "terser-webpack-plugin": "5.2.4",
63
+ "ts-jest": "27.0.3",
64
+ "ts-loader": "9.2.6",
65
+ "tslint": "6.1.3",
66
+ "tslint-config-prettier": "1.18.0",
67
67
  "typedoc": "0.19.2",
68
- "typedoc-neo-theme": "^1.1.1",
69
- "typedoc-plugin-external-module-name": "^4.0.6",
68
+ "typedoc-neo-theme": "1.1.1",
69
+ "typedoc-plugin-external-module-name": "4.0.6",
70
70
  "typescript": "4.3.5",
71
- "webpack": "^5.56.0",
72
- "webpack-cli": "^4.7.2"
71
+ "webpack": "5.56.0",
72
+ "webpack-cli": "4.7.2"
73
73
  },
74
74
  "main": "index.js",
75
75
  "dependencies": {
76
- "@sasjs/utils": "^2.32.0",
77
- "axios": "^0.21.4",
78
- "axios-cookiejar-support": "^1.0.1",
79
- "form-data": "^4.0.0",
80
- "https": "^1.0.0",
81
- "tough-cookie": "^4.0.0"
76
+ "@sasjs/utils": "2.32.0",
77
+ "axios": "0.25.0",
78
+ "axios-cookiejar-support": "1.0.1",
79
+ "form-data": "4.0.0",
80
+ "https": "1.0.0",
81
+ "tough-cookie": "4.0.0"
82
82
  },
83
- "version": "3.2.1"
83
+ "version": "3.4.1"
84
84
  }
package/src/SASjs.ts CHANGED
@@ -736,15 +736,19 @@ export default class SASjs {
736
736
  msg: string
737
737
  } {
738
738
  if (data === null) return { status: true, msg: '' }
739
+
740
+ const isSasFormatsTable = (key: string) =>
741
+ key.match(/^\$.*/) && Object.keys(data).includes(key.replace(/^\$/, ''))
742
+
739
743
  for (const key in data) {
740
- if (!key.match(/^[a-zA-Z_]/)) {
744
+ if (!key.match(/^[a-zA-Z_]/) && !isSasFormatsTable(key)) {
741
745
  return {
742
746
  status: false,
743
747
  msg: 'First letter of table should be alphabet or underscore.'
744
748
  }
745
749
  }
746
750
 
747
- if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
751
+ if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) && !isSasFormatsTable(key)) {
748
752
  return { status: false, msg: 'Table name should be alphanumeric.' }
749
753
  }
750
754
 
@@ -755,7 +759,7 @@ export default class SASjs {
755
759
  }
756
760
  }
757
761
 
758
- if (this.getType(data[key]) !== 'Array') {
762
+ if (this.getType(data[key]) !== 'Array' && !isSasFormatsTable(key)) {
759
763
  return {
760
764
  status: false,
761
765
  msg: 'Parameter data contains invalid table structure.'
@@ -771,6 +775,7 @@ export default class SASjs {
771
775
  }
772
776
  }
773
777
  }
778
+
774
779
  return { status: true, msg: '' }
775
780
  }
776
781
 
@@ -168,7 +168,7 @@ export class SessionManager {
168
168
  ) {
169
169
  if (stateLink) {
170
170
  if (this.debug && !this.printedSessionState.printed) {
171
- logger.info('Polling session status...')
171
+ logger.info(`Polling: ${this.serverUrl + stateLink.href}`)
172
172
 
173
173
  this.printedSessionState.printed = true
174
174
  }
@@ -206,10 +206,11 @@ const doPoll = async (
206
206
 
207
207
  pollCount++
208
208
 
209
+ const jobHref = postedJob.links.find((l: Link) => l.rel === 'self')!.href
210
+
209
211
  if (pollOptions?.streamLog) {
210
- const jobUrl = postedJob.links.find((l: Link) => l.rel === 'self')
211
212
  const { result: job } = await requestClient.get<Job>(
212
- jobUrl!.href,
213
+ jobHref,
213
214
  authConfig?.access_token
214
215
  )
215
216
 
@@ -231,7 +232,7 @@ const doPoll = async (
231
232
  }
232
233
 
233
234
  if (debug && printedState !== state) {
234
- logger.info('Polling job status...')
235
+ logger.info(`Polling: ${requestClient.getBaseUrl() + jobHref}/state`)
235
236
  logger.info(`Current job state: ${state}`)
236
237
 
237
238
  printedState = state
@@ -195,9 +195,7 @@ export class RequestClient implements HttpClient {
195
195
  }
196
196
  ),
197
197
  debug
198
- ).catch((err) => {
199
- throw prefixMessage(err, 'Error while handling error. ')
200
- })
198
+ )
201
199
  })
202
200
  }
203
201
 
@@ -217,6 +215,7 @@ export class RequestClient implements HttpClient {
217
215
  .post<T>(url, data, { headers, withCredentials: true })
218
216
  .then((response) => {
219
217
  throwIfError(response)
218
+
220
219
  return this.parseResponse<T>(response)
221
220
  })
222
221
  .catch(async (e) => {
@@ -466,6 +465,8 @@ export class RequestClient implements HttpClient {
466
465
 
467
466
  if (e instanceof LoginRequiredError) {
468
467
  this.clearCsrfTokens()
468
+
469
+ throw e
469
470
  }
470
471
 
471
472
  if (response?.status === 403 || response?.status === 449) {
@@ -488,7 +489,8 @@ export class RequestClient implements HttpClient {
488
489
  else return
489
490
  }
490
491
 
491
- throw e
492
+ if (e.message) throw e
493
+ else throw prefixMessage(e, 'Error while handling error. ')
492
494
  }
493
495
 
494
496
  protected parseResponse<T>(response: AxiosResponse<any>) {
@@ -540,8 +542,9 @@ export class RequestClient implements HttpClient {
540
542
 
541
543
  this.httpClient = createAxiosInstance(baseUrl, httpsAgent)
542
544
 
543
- this.httpClient.defaults.validateStatus = (status) =>
544
- status >= 200 && status < 401
545
+ this.httpClient.defaults.validateStatus = (status) => {
546
+ return status >= 200 && status <= 401
547
+ }
545
548
  }
546
549
  }
547
550
 
@@ -13,7 +13,7 @@ export class SasjsRequestClient extends RequestClient {
13
13
 
14
14
  headers.Accept = contentType === 'application/json' ? contentType : '*/*'
15
15
 
16
- if (!accessToken)
16
+ if (!accessToken && typeof window !== 'undefined')
17
17
  accessToken = localStorage.getItem('accessToken') ?? undefined
18
18
 
19
19
  if (accessToken) headers.Authorization = `Bearer ${accessToken}`
@@ -18,6 +18,7 @@ app.get('/', function (req: any, res: any) {
18
18
 
19
19
  app.post('/SASLogon/oauth/token', function (req: any, res: any) {
20
20
  let valid = true
21
+
21
22
  // capture the encoded form data
22
23
  req.on('data', (data: any) => {
23
24
  const resData = data.toString()
@@ -2,70 +2,115 @@
2
2
  * Converts the given JSON object array to a CSV string.
3
3
  * @param data - the array of JSON objects to convert.
4
4
  */
5
- export const convertToCSV = (data: any) => {
6
- const replacer = (key: any, value: any) => (value === null ? '' : value)
7
- const headerFields = Object.keys(data[0])
5
+ export const convertToCSV = (
6
+ data: any,
7
+ sasFormats?: { formats: { [key: string]: string } }
8
+ ) => {
9
+ let formats = sasFormats?.formats
10
+ let headers: string[] = []
8
11
  let csvTest
9
12
  let invalidString = false
10
- const headers = headerFields.map((field) => {
11
- let firstFoundType: string | null = null
12
- let hasMixedTypes: boolean = false
13
- let rowNumError: number = -1
14
-
15
- const longestValueForField = data
16
- .map((row: any, index: number) => {
17
- if (row[field] || row[field] === '') {
18
- if (firstFoundType) {
19
- let currentFieldType =
20
- row[field] === '' || typeof row[field] === 'string'
21
- ? 'chars'
22
- : 'number'
23
-
24
- if (!hasMixedTypes) {
25
- hasMixedTypes = currentFieldType !== firstFoundType
26
- rowNumError = hasMixedTypes ? index + 1 : -1
27
- }
28
- } else {
29
- if (row[field] === '') {
30
- firstFoundType = 'chars'
31
- } else {
32
- firstFoundType =
33
- typeof row[field] === 'string' ? 'chars' : 'number'
34
- }
35
- }
13
+ const specialMissingValueRegExp = /^[a-z_]{1}$/i
36
14
 
37
- let byteSize
15
+ if (formats) {
16
+ headers = Object.keys(formats).map((key) => `${key}:${formats![key]}`)
17
+ }
38
18
 
39
- if (typeof row[field] === 'string') {
40
- byteSize = getByteSize(row[field])
41
- }
19
+ const headerFields = Object.keys(data[0])
42
20
 
43
- return byteSize
21
+ headerFields.forEach((field) => {
22
+ if (!formats || !Object.keys(formats).includes(field)) {
23
+ let hasNullOrNumber = false
24
+ let hasSpecialMissingString = false
25
+
26
+ data.forEach((row: { [key: string]: any }) => {
27
+ if (row[field] === null || typeof row[field] === 'number') {
28
+ hasNullOrNumber = true
29
+ } else if (
30
+ typeof row[field] === 'string' &&
31
+ specialMissingValueRegExp.test(row[field])
32
+ ) {
33
+ hasSpecialMissingString = true
44
34
  }
45
35
  })
46
- .sort((a: number, b: number) => b - a)[0]
47
- if (longestValueForField && longestValueForField > 32765) {
48
- invalidString = true
49
- }
50
- if (hasMixedTypes) {
51
- console.error(
52
- `Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
53
- )
54
- }
55
36
 
56
- return `${field}:${firstFoundType === 'chars' ? '$char' : ''}${
57
- longestValueForField
58
- ? longestValueForField
59
- : firstFoundType === 'chars'
60
- ? '1'
61
- : 'best'
62
- }.`
37
+ if (hasNullOrNumber && hasSpecialMissingString) {
38
+ headers.push(`${field}:best.`)
39
+
40
+ if (!formats) formats = {}
41
+
42
+ formats[field] = 'best.'
43
+ } else {
44
+ let firstFoundType: string | null = null
45
+ let hasMixedTypes: boolean = false
46
+ let rowNumError: number = -1
47
+
48
+ const longestValueForField = data
49
+ .map((row: any, index: number) => {
50
+ if (row[field] || row[field] === '') {
51
+ if (firstFoundType) {
52
+ let currentFieldType =
53
+ row[field] === '' || typeof row[field] === 'string'
54
+ ? 'chars'
55
+ : 'number'
56
+
57
+ if (!hasMixedTypes) {
58
+ hasMixedTypes = currentFieldType !== firstFoundType
59
+ rowNumError = hasMixedTypes ? index + 1 : -1
60
+ }
61
+ } else {
62
+ if (row[field] === '') {
63
+ firstFoundType = 'chars'
64
+ } else {
65
+ firstFoundType =
66
+ typeof row[field] === 'string' ? 'chars' : 'number'
67
+ }
68
+ }
69
+
70
+ let byteSize
71
+
72
+ if (typeof row[field] === 'string') {
73
+ byteSize = getByteSize(row[field])
74
+ }
75
+
76
+ return byteSize
77
+ }
78
+ })
79
+ .sort((a: number, b: number) => b - a)[0]
80
+
81
+ if (longestValueForField && longestValueForField > 32765) {
82
+ invalidString = true
83
+ }
84
+
85
+ if (hasMixedTypes) {
86
+ console.error(
87
+ `Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
88
+ )
89
+ }
90
+
91
+ headers.push(
92
+ `${field}:${firstFoundType === 'chars' ? '$char' : ''}${
93
+ longestValueForField
94
+ ? longestValueForField
95
+ : firstFoundType === 'chars'
96
+ ? '1'
97
+ : 'best'
98
+ }.`
99
+ )
100
+ }
101
+ }
63
102
  })
64
103
 
65
- if (invalidString) {
66
- return 'ERROR: LARGE STRING LENGTH'
104
+ if (sasFormats) {
105
+ headers = headers.sort(
106
+ (a, b) =>
107
+ headerFields.indexOf(a.replace(/:.*/, '')) -
108
+ headerFields.indexOf(b.replace(/:.*/, ''))
109
+ )
67
110
  }
68
111
 
112
+ if (invalidString) return 'ERROR: LARGE STRING LENGTH'
113
+
69
114
  csvTest = data.map((row: any) => {
70
115
  const fields = Object.keys(row).map((fieldName, index) => {
71
116
  let value
@@ -76,6 +121,17 @@ export const convertToCSV = (data: any) => {
76
121
  // stringify with replacer converts null values to empty strings
77
122
  value = currentCell === null ? '' : currentCell
78
123
 
124
+ if (formats && formats[fieldName] === 'best.') {
125
+ if (value && !specialMissingValueRegExp.test(value)) {
126
+ console.log(`🤖[value]🤖`, value)
127
+ throw new Error(
128
+ 'Special missing value can only be a single character from A to Z or _'
129
+ )
130
+ }
131
+
132
+ return `.${value.toLowerCase()}`
133
+ }
134
+
79
135
  // if there any present, it should have preceding (") for escaping
80
136
  value = value.replace(/"/g, `""`)
81
137
 
@@ -7,9 +7,17 @@ export const formatDataForRequest = (data: any) => {
7
7
  const result: any = {}
8
8
 
9
9
  for (const tableName in data) {
10
+ if (
11
+ tableName.match(/^\$.*/) &&
12
+ Object.keys(data).includes(tableName.replace(/^\$/, ''))
13
+ ) {
14
+ continue
15
+ }
16
+
10
17
  tableCounter++
11
18
  sasjsTables.push(tableName)
12
- const csv = convertToCSV(data[tableName])
19
+ const csv = convertToCSV(data[tableName], data[`$${tableName}`])
20
+
13
21
  if (csv === 'ERROR: LARGE STRING LENGTH') {
14
22
  throw new Error(
15
23
  'The max length of a string value in SASjs is 32765 characters.'
@@ -27,6 +35,7 @@ export const formatDataForRequest = (data: any) => {
27
35
  result[`sasjs${tableCounter}data`] = csv
28
36
  }
29
37
  }
38
+
30
39
  result['sasjs_tables'] = sasjsTables.join(' ')
31
40
 
32
41
  return result
@@ -0,0 +1 @@
1
+ export {};
@@ -2,4 +2,8 @@
2
2
  * Converts the given JSON object array to a CSV string.
3
3
  * @param data - the array of JSON objects to convert.
4
4
  */
5
- export declare const convertToCSV: (data: any) => string;
5
+ export declare const convertToCSV: (data: any, sasFormats?: {
6
+ formats: {
7
+ [key: string]: string;
8
+ };
9
+ } | undefined) => string;