@sasjs/adapter 4.16.6 → 4.17.0

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.
@@ -13,5 +13,6 @@ export declare class WebJobExecutor extends BaseJobExecutor {
13
13
  private sasViyaApiClient;
14
14
  constructor(serverUrl: string, serverType: ServerType, jobsPath: string, requestClient: RequestClient, sasViyaApiClient: SASViyaApiClient);
15
15
  execute(sasJob: string, data: any, config: any, loginRequiredCallback?: any, authConfig?: AuthConfig, extraResponseAttributes?: ExtraResponseAttributes[]): Promise<unknown>;
16
+ protected getRequestParams(config: any): any;
16
17
  private getJobUri;
17
18
  }
@@ -79,6 +79,14 @@ export declare class SASjsConfig {
79
79
  * may affect browser performance, especially with debug (logs) enabled.
80
80
  */
81
81
  requestHistoryLimit?: number;
82
+ /**
83
+ * Optional setting. When `true`, the request runs as a Viya execution task —
84
+ * appends `_executionTasks=true` to the request URL. Only applies to the Viya
85
+ * web jobs path, i.e. when `serverType === SASVIYA` AND
86
+ * `useComputeApi` is `null`/`undefined`. Has no effect when `useComputeApi`
87
+ * is explicitly set to `true` or `false`.
88
+ */
89
+ runAsTask?: boolean;
82
90
  }
83
91
  export declare enum LoginMechanism {
84
92
  Default = "Default",
@@ -15,6 +15,7 @@ export * from './parseGeneratedCode';
15
15
  export * from './parseSasViyaLog';
16
16
  export * from './parseSourceCode';
17
17
  export * from './parseViyaDebugResponse';
18
+ export * from './parseViyaLogDebugResponse';
18
19
  export * from './parseWeboutResponse';
19
20
  export * from './serialize';
20
21
  export * from './splitChunks';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * When querying a Viya job using the Web approach with _DEBUG=128 (used when
3
+ * runAsTask is true), the webout JSON is inlined into the response via:
4
+ * var blob = new Blob([`{...}`], {type: 'application/json'});
5
+ * On abort/error paths the same shape is used but with text/plain and
6
+ * weboutBEGIN/END markers around the JSON:
7
+ * var blob = new Blob([`>>weboutBEGIN<<\n{...}\n>>weboutEND<<\n`], {type: 'text/plain'});
8
+ * No follow-up request is needed — extract and parse the JSON directly.
9
+ */
10
+ export declare const parseSasViyaLogDebugResponse: (response: any) => Promise<any>;
package/package.json CHANGED
@@ -77,11 +77,11 @@
77
77
  "main": "index.js",
78
78
  "dependencies": {
79
79
  "@sasjs/utils": "^3.5.6",
80
- "axios": "1.15.0",
80
+ "axios": "1.16.0",
81
81
  "axios-cookiejar-support": "5.0.5",
82
82
  "form-data": "4.0.4",
83
83
  "https": "1.0.0",
84
84
  "tough-cookie": "4.1.3"
85
85
  },
86
- "version": "4.16.6"
86
+ "version": "4.17.0"
87
87
  }
@@ -1031,11 +1031,17 @@ export class SASViyaApiClient {
1031
1031
  jobArguments[`_webin_name${index + 1}`] = fileInfo.tableName
1032
1032
  })
1033
1033
 
1034
+ // Viya JES requires arguments to be Map<String,String>; coerce booleans/numbers.
1035
+ const stringifiedArguments: { [key: string]: string } = {}
1036
+ for (const k of Object.keys(jobArguments)) {
1037
+ stringifiedArguments[k] = String(jobArguments[k])
1038
+ }
1039
+
1034
1040
  const postJobRequestBody = {
1035
1041
  name: `exec-${jobName}`,
1036
1042
  description: 'Powered by SASjs',
1037
1043
  jobDefinition,
1038
- arguments: jobArguments
1044
+ arguments: stringifiedArguments
1039
1045
  }
1040
1046
 
1041
1047
  const { result: postedJob } = await this.requestClient.post<Job>(
@@ -1,6 +1,6 @@
1
1
  import { prefixMessage } from '@sasjs/utils/error'
2
2
  import { RequestClient } from '../../request/RequestClient'
3
- import { convertToCSV } from '../../utils/convertToCsv'
3
+ import { convertToCSV, isFormatsTable } from '../../utils/convertToCsv'
4
4
 
5
5
  /**
6
6
  * Uploads tables to SAS as specially formatted CSVs.
@@ -18,6 +18,10 @@ export async function uploadTables(
18
18
  const uploadedFiles = []
19
19
 
20
20
  for (const tableName in data) {
21
+ // $tablename keys carry only column-format metadata for the matching
22
+ // tablename payload; they must not be uploaded as separate files.
23
+ if (isFormatsTable(tableName)) continue
24
+
21
25
  const csv = convertToCSV(data, tableName)
22
26
  if (csv === 'ERROR: LARGE STRING LENGTH') {
23
27
  throw new Error(
@@ -11,6 +11,13 @@ export const generateTableUploadForm = (
11
11
  let tableCounter = 0
12
12
 
13
13
  for (const tableName in data) {
14
+ if (
15
+ isFormatsTable(tableName) &&
16
+ Object.keys(data).includes(tableName.replace(/^\$/, ''))
17
+ ) {
18
+ continue
19
+ }
20
+
14
21
  tableCounter++
15
22
 
16
23
  // Formats table should not be sent as part of 'sasjs_tables'
@@ -55,7 +55,6 @@ export abstract class BaseJobExecutor implements JobExecutor {
55
55
  if (config.debug) {
56
56
  requestParams['_omittextlog'] = 'false'
57
57
  requestParams['_omitSessionResults'] = 'false'
58
-
59
58
  requestParams['_debug'] = 131
60
59
  }
61
60
 
@@ -16,9 +16,11 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
16
16
  import {
17
17
  isRelativePath,
18
18
  parseSasViyaDebugResponse,
19
+ parseSasViyaLogDebugResponse,
19
20
  appendExtraResponseAttributes,
20
21
  parseWeboutResponse,
21
- getFormData
22
+ getFormData,
23
+ isNode
22
24
  } from '../utils'
23
25
  import { BaseJobExecutor } from './JobExecutor'
24
26
 
@@ -101,6 +103,10 @@ export class WebJobExecutor extends BaseJobExecutor {
101
103
  apiUrl += config.contextName?.trim()
102
104
  ? `&_contextname=${encodeURIComponent(config.contextName)}`
103
105
  : ''
106
+
107
+ if (config.runAsTask === true) {
108
+ apiUrl += '&_executionTasks=true'
109
+ }
104
110
  }
105
111
 
106
112
  let requestParams = {
@@ -113,6 +119,26 @@ export class WebJobExecutor extends BaseJobExecutor {
113
119
  */
114
120
  let formData = getFormData()
115
121
 
122
+ // FIXME(viya - SAS Track CS0409737): remove when Viya stops rejecting empty multipart on
123
+ // _executionTasks=true. Dummy file keeps the body non-empty
124
+ const hasExecutionTasksFlag = config.runAsTask === true
125
+
126
+ // Move debug params to URL for viya; viya seems to honor them more
127
+ // reliably in the query string than the multipart body.
128
+ if (
129
+ hasExecutionTasksFlag &&
130
+ config.debug &&
131
+ config.serverType === ServerType.SasViya
132
+ ) {
133
+ const debugKeys = ['_debug', '_omitSessionResults']
134
+ debugKeys.forEach((key) => {
135
+ if (requestParams[key] !== undefined) {
136
+ apiUrl += `&${key}=${encodeURIComponent(requestParams[key])}`
137
+ delete requestParams[key]
138
+ }
139
+ })
140
+ }
141
+
116
142
  if (data) {
117
143
  const stringifiedData = JSON.stringify(data)
118
144
  if (
@@ -133,10 +159,21 @@ export class WebJobExecutor extends BaseJobExecutor {
133
159
  generateTableUploadForm(formData, data)
134
160
  formData = newFormData
135
161
  requestParams = { ...requestParams, ...params }
162
+
163
+ if (
164
+ config.serverType === ServerType.SasViya &&
165
+ hasExecutionTasksFlag
166
+ ) {
167
+ addDummyFile(formData)
168
+ }
136
169
  } catch (e: any) {
137
170
  return Promise.reject(new ErrorResponse(e?.message, e))
138
171
  }
139
172
  }
173
+ } else {
174
+ if (config.serverType === ServerType.SasViya && hasExecutionTasksFlag) {
175
+ addDummyFile(formData)
176
+ }
140
177
  }
141
178
 
142
179
  for (const key in requestParams) {
@@ -168,11 +205,14 @@ export class WebJobExecutor extends BaseJobExecutor {
168
205
  if (config.debug) {
169
206
  switch (this.serverType) {
170
207
  case ServerType.SasViya:
171
- jsonResponse = await parseSasViyaDebugResponse(
172
- res.result,
173
- this.requestClient,
174
- this.serverUrl
175
- )
208
+ jsonResponse =
209
+ config.useComputeApi === null && config.runAsTask === true
210
+ ? await parseSasViyaLogDebugResponse(res.result)
211
+ : await parseSasViyaDebugResponse(
212
+ res.result,
213
+ this.requestClient,
214
+ this.serverUrl
215
+ )
176
216
  break
177
217
  case ServerType.Sas9:
178
218
  jsonResponse =
@@ -231,6 +271,23 @@ export class WebJobExecutor extends BaseJobExecutor {
231
271
  return requestPromise
232
272
  }
233
273
 
274
+ protected getRequestParams(config: any): any {
275
+ const requestParams = super.getRequestParams(config)
276
+
277
+ // FIXME(viya - possible issue with default debug flags)
278
+ // runAsTask on Viya: use _debug=128 (not 131) and omit _omittextlog
279
+ if (
280
+ config.debug &&
281
+ config.serverType === ServerType.SasViya &&
282
+ config.runAsTask === true
283
+ ) {
284
+ requestParams['_debug'] = 128
285
+ delete requestParams['_omittextlog']
286
+ }
287
+
288
+ return requestParams
289
+ }
290
+
234
291
  private async getJobUri(sasJob: string) {
235
292
  if (!this.sasViyaApiClient) return ''
236
293
  let uri = ''
@@ -263,3 +320,18 @@ export class WebJobExecutor extends BaseJobExecutor {
263
320
  return uri
264
321
  }
265
322
  }
323
+
324
+ function addDummyFile(formData: NodeFormData | FormData) {
325
+ if (isNode()) {
326
+ ;(formData as NodeFormData).append('_sasjs_noop', '', {
327
+ filename: '_sasjs_noop.txt',
328
+ contentType: 'text/plain'
329
+ })
330
+ } else {
331
+ ;(formData as FormData).append(
332
+ '_sasjs_noop',
333
+ new Blob([''], { type: 'text/plain' }),
334
+ '_sasjs_noop.txt'
335
+ )
336
+ }
337
+ }
@@ -79,6 +79,14 @@ export class SASjsConfig {
79
79
  * may affect browser performance, especially with debug (logs) enabled.
80
80
  */
81
81
  requestHistoryLimit?: number = 10
82
+ /**
83
+ * Optional setting. When `true`, the request runs as a Viya execution task —
84
+ * appends `_executionTasks=true` to the request URL. Only applies to the Viya
85
+ * web jobs path, i.e. when `serverType === SASVIYA` AND
86
+ * `useComputeApi` is `null`/`undefined`. Has no effect when `useComputeApi`
87
+ * is explicitly set to `true` or `false`.
88
+ */
89
+ runAsTask?: boolean = false
82
90
  }
83
91
 
84
92
  export enum LoginMechanism {
@@ -15,6 +15,7 @@ export * from './parseGeneratedCode'
15
15
  export * from './parseSasViyaLog'
16
16
  export * from './parseSourceCode'
17
17
  export * from './parseViyaDebugResponse'
18
+ export * from './parseViyaLogDebugResponse'
18
19
  export * from './parseWeboutResponse'
19
20
  export * from './serialize'
20
21
  export * from './splitChunks'
@@ -0,0 +1,32 @@
1
+ import { getValidJson } from './getValidJson'
2
+ import { parseWeboutResponse } from './parseWeboutResponse'
3
+
4
+ /**
5
+ * When querying a Viya job using the Web approach with _DEBUG=128 (used when
6
+ * runAsTask is true), the webout JSON is inlined into the response via:
7
+ * var blob = new Blob([`{...}`], {type: 'application/json'});
8
+ * On abort/error paths the same shape is used but with text/plain and
9
+ * weboutBEGIN/END markers around the JSON:
10
+ * var blob = new Blob([`>>weboutBEGIN<<\n{...}\n>>weboutEND<<\n`], {type: 'text/plain'});
11
+ * No follow-up request is needed — extract and parse the JSON directly.
12
+ */
13
+ export const parseSasViyaLogDebugResponse = async (response: any) => {
14
+ // If upstream already parsed the response as JSON (object), pass through.
15
+ if (typeof response !== 'string') {
16
+ return response
17
+ }
18
+
19
+ const blobMatch = response.match(
20
+ /new Blob\(\[`([\s\S]*?)`\],\s*\{type:\s*'(?:application\/json|text\/plain)'\}\)/
21
+ )
22
+ if (!blobMatch) {
23
+ throw new Error('Unable to find webout blob in debug log response.')
24
+ }
25
+
26
+ const blobContent = blobMatch[1]
27
+ const stripped = blobContent.includes('>>weboutBEGIN<<')
28
+ ? parseWeboutResponse(blobContent)
29
+ : blobContent
30
+
31
+ return getValidJson(stripped)
32
+ }
@@ -79,6 +79,14 @@ export declare class SASjsConfig {
79
79
  * may affect browser performance, especially with debug (logs) enabled.
80
80
  */
81
81
  requestHistoryLimit?: number;
82
+ /**
83
+ * Optional setting. When `true`, the request runs as a Viya execution task —
84
+ * appends `_executionTasks=true` to the request URL. Only applies to the Viya
85
+ * web jobs path, i.e. when `serverType === SASVIYA` AND
86
+ * `useComputeApi` is `null`/`undefined`. Has no effect when `useComputeApi`
87
+ * is explicitly set to `true` or `false`.
88
+ */
89
+ runAsTask?: boolean;
82
90
  }
83
91
  export declare enum LoginMechanism {
84
92
  Default = "Default",
package/utils/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export * from './parseGeneratedCode';
15
15
  export * from './parseSasViyaLog';
16
16
  export * from './parseSourceCode';
17
17
  export * from './parseViyaDebugResponse';
18
+ export * from './parseViyaLogDebugResponse';
18
19
  export * from './parseWeboutResponse';
19
20
  export * from './serialize';
20
21
  export * from './splitChunks';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * When querying a Viya job using the Web approach with _DEBUG=128 (used when
3
+ * runAsTask is true), the webout JSON is inlined into the response via:
4
+ * var blob = new Blob([`{...}`], {type: 'application/json'});
5
+ * On abort/error paths the same shape is used but with text/plain and
6
+ * weboutBEGIN/END markers around the JSON:
7
+ * var blob = new Blob([`>>weboutBEGIN<<\n{...}\n>>weboutEND<<\n`], {type: 'text/plain'});
8
+ * No follow-up request is needed — extract and parse the JSON directly.
9
+ */
10
+ export declare const parseSasViyaLogDebugResponse: (response: any) => Promise<any>;
@@ -0,0 +1 @@
1
+ export {};