@sasjs/adapter 3.4.2 → 3.6.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.
- package/SASjs.d.ts +17 -9
- package/SASjsApiClient.d.ts +1 -0
- package/file/spec/generateFileUploadForm.spec.d.ts +1 -0
- package/index.js +2 -2
- package/job-execution/SasJsJobExecutor.d.ts +10 -0
- package/job-execution/index.d.ts +2 -1
- package/node/SASjs.d.ts +17 -9
- package/node/SASjsApiClient.d.ts +1 -0
- package/node/index.js +2 -2
- package/node/job-execution/SasJsJobExecutor.d.ts +10 -0
- package/node/job-execution/index.d.ts +2 -1
- package/node/types/SASjsConfig.d.ts +1 -1
- package/node/utils/convertToCsv.d.ts +1 -1
- package/package.json +3 -3
- package/src/SASjs.ts +58 -34
- package/src/SASjsApiClient.ts +6 -0
- package/src/file/generateFileUploadForm.ts +3 -0
- package/src/job-execution/SasJsJobExecutor.ts +123 -0
- package/src/job-execution/index.ts +2 -1
- package/src/request/Sas9RequestClient.ts +2 -2
- package/src/types/SASjsConfig.ts +1 -1
- package/src/utils/convertToCsv.ts +2 -2
- package/src/utils/parseWeboutResponse.ts +1 -0
- package/types/SASjsConfig.d.ts +1 -1
- package/utils/convertToCsv.d.ts +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AuthConfig, ExtraResponseAttributes, ServerType } from '@sasjs/utils/types';
|
|
2
|
+
import { RequestClient } from '../request/RequestClient';
|
|
3
|
+
import { BaseJobExecutor } from './JobExecutor';
|
|
4
|
+
export declare class SasJsJobExecutor extends BaseJobExecutor {
|
|
5
|
+
private jobsPath;
|
|
6
|
+
private requestClient;
|
|
7
|
+
constructor(serverUrl: string, serverType: ServerType, jobsPath: string, requestClient: RequestClient);
|
|
8
|
+
execute(sasJob: string, data: any, config: any, loginRequiredCallback?: any, authConfig?: AuthConfig, extraResponseAttributes?: ExtraResponseAttributes[]): Promise<unknown>;
|
|
9
|
+
private getRequestParams;
|
|
10
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './ComputeJobExecutor';
|
|
2
|
+
export * from './FileUploader';
|
|
2
3
|
export * from './JesJobExecutor';
|
|
3
4
|
export * from './JobExecutor';
|
|
4
5
|
export * from './Sas9JobExecutor';
|
|
6
|
+
export * from './SasJsJobExecutor';
|
|
5
7
|
export * from './WebJobExecutor';
|
|
6
|
-
export * from './FileUploader';
|
|
@@ -16,7 +16,7 @@ export declare class SASjsConfig {
|
|
|
16
16
|
* The location of the STP Process Web Application. By default the adapter
|
|
17
17
|
* will use '/SASjsApi/stp/execute' on SAS JS.
|
|
18
18
|
*/
|
|
19
|
-
pathSASJS
|
|
19
|
+
pathSASJS: string;
|
|
20
20
|
/**
|
|
21
21
|
* The location of the Stored Process Web Application. By default the adapter
|
|
22
22
|
* will use '/SASStoredProcess/do' on SAS 9.
|
|
@@ -2,7 +2,7 @@
|
|
|
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, sasFormats?: {
|
|
5
|
+
export declare const convertToCSV: (data: any[], sasFormats?: {
|
|
6
6
|
formats: {
|
|
7
7
|
[key: string]: string;
|
|
8
8
|
};
|
package/package.json
CHANGED
|
@@ -74,11 +74,11 @@
|
|
|
74
74
|
"main": "index.js",
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@sasjs/utils": "2.35.0",
|
|
77
|
-
"axios": "0.
|
|
78
|
-
"axios-cookiejar-support": "
|
|
77
|
+
"axios": "0.26.0",
|
|
78
|
+
"axios-cookiejar-support": "1.0.1",
|
|
79
79
|
"form-data": "4.0.0",
|
|
80
80
|
"https": "1.0.0",
|
|
81
81
|
"tough-cookie": "4.0.0"
|
|
82
82
|
},
|
|
83
|
-
"version": "3.
|
|
83
|
+
"version": "3.6.1"
|
|
84
84
|
}
|
package/src/SASjs.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
ComputeJobExecutor,
|
|
28
28
|
JesJobExecutor,
|
|
29
29
|
Sas9JobExecutor,
|
|
30
|
+
SasJsJobExecutor,
|
|
30
31
|
FileUploader
|
|
31
32
|
} from './job-execution'
|
|
32
33
|
import { ErrorResponse } from './types/errors'
|
|
@@ -62,6 +63,7 @@ export default class SASjs {
|
|
|
62
63
|
private computeJobExecutor: JobExecutor | null = null
|
|
63
64
|
private jesJobExecutor: JobExecutor | null = null
|
|
64
65
|
private sas9JobExecutor: JobExecutor | null = null
|
|
66
|
+
private sasJsJobExecutor: JobExecutor | null = null
|
|
65
67
|
|
|
66
68
|
constructor(config?: Partial<SASjsConfig>) {
|
|
67
69
|
this.sasjsConfig = {
|
|
@@ -76,6 +78,12 @@ export default class SASjs {
|
|
|
76
78
|
return this.requestClient?.getCsrfToken(type)
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Executes the sas code against SAS9 server
|
|
83
|
+
* @param linesOfCode - lines of sas code from the file to run.
|
|
84
|
+
* @param username - a string representing the username.
|
|
85
|
+
* @param password - a string representing the password.
|
|
86
|
+
*/
|
|
79
87
|
public async executeScriptSAS9(
|
|
80
88
|
linesOfCode: string[],
|
|
81
89
|
userName: string,
|
|
@@ -90,6 +98,38 @@ export default class SASjs {
|
|
|
90
98
|
)
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Executes the sas code against SASViya server
|
|
103
|
+
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
|
104
|
+
* @param linesOfCode - lines of sas code from the file to run.
|
|
105
|
+
* @param contextName - context name on which code will be run on the server.
|
|
106
|
+
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
|
|
107
|
+
* @param debug - (optional) if true, global debug config will be overriden
|
|
108
|
+
*/
|
|
109
|
+
public async executeScriptSASViya(
|
|
110
|
+
fileName: string,
|
|
111
|
+
linesOfCode: string[],
|
|
112
|
+
contextName: string,
|
|
113
|
+
authConfig?: AuthConfig,
|
|
114
|
+
debug?: boolean
|
|
115
|
+
) {
|
|
116
|
+
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
|
|
117
|
+
if (!contextName) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return await this.sasViyaApiClient!.executeScript(
|
|
124
|
+
fileName,
|
|
125
|
+
linesOfCode,
|
|
126
|
+
contextName,
|
|
127
|
+
authConfig,
|
|
128
|
+
null,
|
|
129
|
+
debug ? debug : this.sasjsConfig.debug
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
93
133
|
/**
|
|
94
134
|
* Gets compute contexts.
|
|
95
135
|
* @param accessToken - an access token for an authorized user.
|
|
@@ -253,38 +293,6 @@ export default class SASjs {
|
|
|
253
293
|
return await this.sasViyaApiClient!.createSession(contextName, accessToken)
|
|
254
294
|
}
|
|
255
295
|
|
|
256
|
-
/**
|
|
257
|
-
* Executes the sas code against given sas server
|
|
258
|
-
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
|
|
259
|
-
* @param linesOfCode - lines of sas code from the file to run.
|
|
260
|
-
* @param contextName - context name on which code will be run on the server.
|
|
261
|
-
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
|
|
262
|
-
* @param debug - (optional) if true, global debug config will be overriden
|
|
263
|
-
*/
|
|
264
|
-
public async executeScriptSASViya(
|
|
265
|
-
fileName: string,
|
|
266
|
-
linesOfCode: string[],
|
|
267
|
-
contextName: string,
|
|
268
|
-
authConfig?: AuthConfig,
|
|
269
|
-
debug?: boolean
|
|
270
|
-
) {
|
|
271
|
-
this.isMethodSupported('executeScriptSASViya', [ServerType.SasViya])
|
|
272
|
-
if (!contextName) {
|
|
273
|
-
throw new Error(
|
|
274
|
-
'Context name is undefined. Please set a `contextName` in your SASjs or override config.'
|
|
275
|
-
)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return await this.sasViyaApiClient!.executeScript(
|
|
279
|
-
fileName,
|
|
280
|
-
linesOfCode,
|
|
281
|
-
contextName,
|
|
282
|
-
authConfig,
|
|
283
|
-
null,
|
|
284
|
-
debug ? debug : this.sasjsConfig.debug
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
296
|
/**
|
|
289
297
|
* Creates a folder in the logical SAS folder tree
|
|
290
298
|
* @param folderName - name of the folder to be created.
|
|
@@ -675,7 +683,16 @@ export default class SASjs {
|
|
|
675
683
|
const validationResult = this.validateInput(data)
|
|
676
684
|
|
|
677
685
|
if (validationResult.status) {
|
|
678
|
-
if (
|
|
686
|
+
if (config.serverType === ServerType.Sasjs) {
|
|
687
|
+
return await this.sasJsJobExecutor!.execute(
|
|
688
|
+
sasJob,
|
|
689
|
+
data,
|
|
690
|
+
config,
|
|
691
|
+
loginRequiredCallback,
|
|
692
|
+
authConfig,
|
|
693
|
+
extraResponseAttributes
|
|
694
|
+
)
|
|
695
|
+
} else if (
|
|
679
696
|
config.serverType !== ServerType.Sas9 &&
|
|
680
697
|
config.useComputeApi !== undefined &&
|
|
681
698
|
config.useComputeApi !== null
|
|
@@ -1031,7 +1048,7 @@ export default class SASjs {
|
|
|
1031
1048
|
? this.sasjsConfig.pathSASViya
|
|
1032
1049
|
: this.sasjsConfig.serverType === ServerType.Sas9
|
|
1033
1050
|
? this.sasjsConfig.pathSAS9
|
|
1034
|
-
: this.sasjsConfig.pathSASJS
|
|
1051
|
+
: this.sasjsConfig.pathSASJS
|
|
1035
1052
|
|
|
1036
1053
|
this.authManager = new AuthManager(
|
|
1037
1054
|
this.sasjsConfig.serverUrl,
|
|
@@ -1096,6 +1113,13 @@ export default class SASjs {
|
|
|
1096
1113
|
this.sasViyaApiClient!
|
|
1097
1114
|
)
|
|
1098
1115
|
|
|
1116
|
+
this.sasJsJobExecutor = new SasJsJobExecutor(
|
|
1117
|
+
this.sasjsConfig.serverUrl,
|
|
1118
|
+
this.sasjsConfig.serverType!,
|
|
1119
|
+
this.jobsPath,
|
|
1120
|
+
this.requestClient
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1099
1123
|
this.sas9JobExecutor = new Sas9JobExecutor(
|
|
1100
1124
|
this.sasjsConfig.serverUrl,
|
|
1101
1125
|
this.sasjsConfig.serverType!,
|
package/src/SASjsApiClient.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { RequestClient } from './request/RequestClient'
|
|
|
4
4
|
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
|
5
5
|
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
|
6
6
|
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
|
|
7
|
+
import { parseWeboutResponse } from './utils'
|
|
7
8
|
import { getTokens } from './auth/getTokens'
|
|
8
9
|
|
|
9
10
|
export class SASjsApiClient {
|
|
@@ -49,8 +50,13 @@ export class SASjsApiClient {
|
|
|
49
50
|
log?: string
|
|
50
51
|
logPath?: string
|
|
51
52
|
error?: {}
|
|
53
|
+
_webout?: string
|
|
52
54
|
}>('SASjsApi/stp/execute', query, undefined)
|
|
53
55
|
|
|
56
|
+
if (Object.keys(result).includes('_webout')) {
|
|
57
|
+
result._webout = parseWeboutResponse(result._webout!)
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
return Promise.resolve(result)
|
|
55
61
|
}
|
|
56
62
|
|
|
@@ -5,8 +5,11 @@ export const generateFileUploadForm = (
|
|
|
5
5
|
data: any
|
|
6
6
|
): FormData => {
|
|
7
7
|
for (const tableName in data) {
|
|
8
|
+
if (!Array.isArray(data[tableName])) continue
|
|
9
|
+
|
|
8
10
|
const name = tableName
|
|
9
11
|
const csv = convertToCSV(data[tableName])
|
|
12
|
+
|
|
10
13
|
if (csv === 'ERROR: LARGE STRING LENGTH') {
|
|
11
14
|
throw new Error(
|
|
12
15
|
'The max length of a string value in SASjs is 32765 characters.'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthConfig,
|
|
3
|
+
ExtraResponseAttributes,
|
|
4
|
+
ServerType
|
|
5
|
+
} from '@sasjs/utils/types'
|
|
6
|
+
import {
|
|
7
|
+
ErrorResponse,
|
|
8
|
+
JobExecutionError,
|
|
9
|
+
LoginRequiredError
|
|
10
|
+
} from '../types/errors'
|
|
11
|
+
import { RequestClient } from '../request/RequestClient'
|
|
12
|
+
import {
|
|
13
|
+
isRelativePath,
|
|
14
|
+
appendExtraResponseAttributes,
|
|
15
|
+
getValidJson
|
|
16
|
+
} from '../utils'
|
|
17
|
+
import { BaseJobExecutor } from './JobExecutor'
|
|
18
|
+
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
|
19
|
+
|
|
20
|
+
export class SasJsJobExecutor extends BaseJobExecutor {
|
|
21
|
+
constructor(
|
|
22
|
+
serverUrl: string,
|
|
23
|
+
serverType: ServerType,
|
|
24
|
+
private jobsPath: string,
|
|
25
|
+
private requestClient: RequestClient
|
|
26
|
+
) {
|
|
27
|
+
super(serverUrl, serverType)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async execute(
|
|
31
|
+
sasJob: string,
|
|
32
|
+
data: any,
|
|
33
|
+
config: any,
|
|
34
|
+
loginRequiredCallback?: any,
|
|
35
|
+
authConfig?: AuthConfig,
|
|
36
|
+
extraResponseAttributes: ExtraResponseAttributes[] = []
|
|
37
|
+
) {
|
|
38
|
+
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
|
39
|
+
const program = isRelativePath(sasJob)
|
|
40
|
+
? config.appLoc
|
|
41
|
+
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
|
42
|
+
: sasJob
|
|
43
|
+
: sasJob
|
|
44
|
+
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
|
45
|
+
|
|
46
|
+
const requestParams = this.getRequestParams(config)
|
|
47
|
+
|
|
48
|
+
const requestPromise = new Promise((resolve, reject) => {
|
|
49
|
+
this.requestClient!.post(
|
|
50
|
+
apiUrl,
|
|
51
|
+
{ ...requestParams, ...data },
|
|
52
|
+
authConfig?.access_token
|
|
53
|
+
)
|
|
54
|
+
.then(async (res: any) => {
|
|
55
|
+
const resObj = {
|
|
56
|
+
result: res.result._webout,
|
|
57
|
+
log: res.result.log
|
|
58
|
+
}
|
|
59
|
+
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
|
|
60
|
+
|
|
61
|
+
let jsonResponse = res.result
|
|
62
|
+
|
|
63
|
+
if (config.debug) {
|
|
64
|
+
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
|
65
|
+
jsonResponse = getValidJson(webout)
|
|
66
|
+
} else {
|
|
67
|
+
jsonResponse = getValidJson(res.result._webout)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const responseObject = appendExtraResponseAttributes(
|
|
71
|
+
{ result: jsonResponse },
|
|
72
|
+
extraResponseAttributes
|
|
73
|
+
)
|
|
74
|
+
resolve(responseObject)
|
|
75
|
+
})
|
|
76
|
+
.catch(async (e: Error) => {
|
|
77
|
+
if (e instanceof JobExecutionError) {
|
|
78
|
+
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
|
79
|
+
reject(new ErrorResponse(e?.message, e))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (e instanceof LoginRequiredError) {
|
|
83
|
+
this.appendWaitingRequest(() => {
|
|
84
|
+
return this.execute(
|
|
85
|
+
sasJob,
|
|
86
|
+
data,
|
|
87
|
+
config,
|
|
88
|
+
loginRequiredCallback,
|
|
89
|
+
authConfig,
|
|
90
|
+
extraResponseAttributes
|
|
91
|
+
).then(
|
|
92
|
+
(res: any) => {
|
|
93
|
+
resolve(res)
|
|
94
|
+
},
|
|
95
|
+
(err: any) => {
|
|
96
|
+
reject(err)
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
await loginCallback()
|
|
102
|
+
} else {
|
|
103
|
+
reject(new ErrorResponse(e?.message, e))
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return requestPromise
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getRequestParams(config: any): any {
|
|
112
|
+
const requestParams: any = {}
|
|
113
|
+
|
|
114
|
+
if (config.debug) {
|
|
115
|
+
requestParams['_omittextlog'] = 'false'
|
|
116
|
+
requestParams['_omitsessionresults'] = 'false'
|
|
117
|
+
|
|
118
|
+
requestParams['_debug'] = 131
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return requestParams
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './ComputeJobExecutor'
|
|
2
|
+
export * from './FileUploader'
|
|
2
3
|
export * from './JesJobExecutor'
|
|
3
4
|
export * from './JobExecutor'
|
|
4
5
|
export * from './Sas9JobExecutor'
|
|
6
|
+
export * from './SasJsJobExecutor'
|
|
5
7
|
export * from './WebJobExecutor'
|
|
6
|
-
export * from './FileUploader'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as https from 'https'
|
|
2
2
|
import { AxiosRequestConfig } from 'axios'
|
|
3
|
-
import
|
|
3
|
+
import axiosCookieJarSupport from 'axios-cookiejar-support'
|
|
4
4
|
import * as tough from 'tough-cookie'
|
|
5
5
|
import { prefixMessage } from '@sasjs/utils/error'
|
|
6
6
|
import { RequestClient, throwIfError } from './RequestClient'
|
|
@@ -17,7 +17,7 @@ export class Sas9RequestClient extends RequestClient {
|
|
|
17
17
|
status >= 200 && status < 303
|
|
18
18
|
|
|
19
19
|
if (axiosCookieJarSupport) {
|
|
20
|
-
axiosCookieJarSupport
|
|
20
|
+
axiosCookieJarSupport(this.httpClient)
|
|
21
21
|
this.httpClient.defaults.jar = new tough.CookieJar()
|
|
22
22
|
}
|
|
23
23
|
}
|
package/src/types/SASjsConfig.ts
CHANGED
|
@@ -16,7 +16,7 @@ export class SASjsConfig {
|
|
|
16
16
|
* The location of the STP Process Web Application. By default the adapter
|
|
17
17
|
* will use '/SASjsApi/stp/execute' on SAS JS.
|
|
18
18
|
*/
|
|
19
|
-
pathSASJS
|
|
19
|
+
pathSASJS: string = ''
|
|
20
20
|
/**
|
|
21
21
|
* The location of the Stored Process Web Application. By default the adapter
|
|
22
22
|
* will use '/SASStoredProcess/do' on SAS 9.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @param data - the array of JSON objects to convert.
|
|
4
4
|
*/
|
|
5
5
|
export const convertToCSV = (
|
|
6
|
-
data: any,
|
|
6
|
+
data: any[],
|
|
7
7
|
sasFormats?: { formats: { [key: string]: string } }
|
|
8
8
|
) => {
|
|
9
9
|
let formats = sasFormats?.formats
|
|
@@ -76,7 +76,7 @@ export const convertToCSV = (
|
|
|
76
76
|
return byteSize
|
|
77
77
|
}
|
|
78
78
|
})
|
|
79
|
-
.sort((a:
|
|
79
|
+
.sort((a: any, b: any) => b - a)[0]
|
|
80
80
|
|
|
81
81
|
if (longestValueForField && longestValueForField > 32765) {
|
|
82
82
|
invalidString = true
|
package/types/SASjsConfig.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class SASjsConfig {
|
|
|
16
16
|
* The location of the STP Process Web Application. By default the adapter
|
|
17
17
|
* will use '/SASjsApi/stp/execute' on SAS JS.
|
|
18
18
|
*/
|
|
19
|
-
pathSASJS
|
|
19
|
+
pathSASJS: string;
|
|
20
20
|
/**
|
|
21
21
|
* The location of the Stored Process Web Application. By default the adapter
|
|
22
22
|
* will use '/SASStoredProcess/do' on SAS 9.
|
package/utils/convertToCsv.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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, sasFormats?: {
|
|
5
|
+
export declare const convertToCSV: (data: any[], sasFormats?: {
|
|
6
6
|
formats: {
|
|
7
7
|
[key: string]: string;
|
|
8
8
|
};
|