tm1npm 1.5.3 → 2.0.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.
- package/CHANGELOG.md +89 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/services/ApplicationService.d.ts +19 -3
- package/lib/services/ApplicationService.d.ts.map +1 -1
- package/lib/services/ApplicationService.js +232 -6
- package/lib/services/AsyncOperationService.d.ts +8 -1
- package/lib/services/AsyncOperationService.d.ts.map +1 -1
- package/lib/services/AsyncOperationService.js +69 -26
- package/lib/services/ElementService.d.ts +67 -1
- package/lib/services/ElementService.d.ts.map +1 -1
- package/lib/services/ElementService.js +214 -0
- package/lib/services/FileService.d.ts.map +1 -1
- package/lib/services/HierarchyService.d.ts +26 -0
- package/lib/services/HierarchyService.d.ts.map +1 -1
- package/lib/services/HierarchyService.js +306 -0
- package/lib/services/ProcessService.d.ts +40 -22
- package/lib/services/ProcessService.d.ts.map +1 -1
- package/lib/services/ProcessService.js +118 -111
- package/lib/services/RestService.d.ts +213 -25
- package/lib/services/RestService.d.ts.map +1 -1
- package/lib/services/RestService.js +841 -263
- package/lib/services/SubsetService.d.ts +2 -0
- package/lib/services/SubsetService.d.ts.map +1 -1
- package/lib/services/SubsetService.js +33 -0
- package/lib/services/TM1Service.d.ts +44 -1
- package/lib/services/TM1Service.d.ts.map +1 -1
- package/lib/services/TM1Service.js +96 -4
- package/lib/services/index.d.ts +1 -1
- package/lib/services/index.d.ts.map +1 -1
- package/lib/tests/100PercentParityCheck.test.js +23 -6
- package/lib/tests/applicationService.issue38.test.d.ts +5 -0
- package/lib/tests/applicationService.issue38.test.d.ts.map +1 -0
- package/lib/tests/applicationService.issue38.test.js +237 -0
- package/lib/tests/asyncOperationService.test.js +51 -45
- package/lib/tests/bugfix28.test.js +12 -4
- package/lib/tests/elementService.issue37.test.d.ts +5 -0
- package/lib/tests/elementService.issue37.test.d.ts.map +1 -0
- package/lib/tests/elementService.issue37.test.js +413 -0
- package/lib/tests/elementService.issue38.test.d.ts +5 -0
- package/lib/tests/elementService.issue38.test.d.ts.map +1 -0
- package/lib/tests/elementService.issue38.test.js +79 -0
- package/lib/tests/hierarchyService.issue38.test.d.ts +5 -0
- package/lib/tests/hierarchyService.issue38.test.d.ts.map +1 -0
- package/lib/tests/hierarchyService.issue38.test.js +460 -0
- package/lib/tests/processService.comprehensive.test.js +9 -9
- package/lib/tests/processService.test.js +234 -0
- package/lib/tests/restService.test.d.ts +0 -4
- package/lib/tests/restService.test.d.ts.map +1 -1
- package/lib/tests/restService.test.js +1558 -143
- package/lib/tests/subsetService.issue38.test.d.ts +5 -0
- package/lib/tests/subsetService.issue38.test.d.ts.map +1 -0
- package/lib/tests/subsetService.issue38.test.js +113 -0
- package/lib/tests/tm1Service.test.js +80 -8
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/services/ApplicationService.ts +282 -10
- package/src/services/AsyncOperationService.ts +76 -29
- package/src/services/ElementService.ts +322 -1
- package/src/services/FileService.ts +3 -3
- package/src/services/HierarchyService.ts +419 -1
- package/src/services/ProcessService.ts +185 -142
- package/src/services/RestService.ts +1021 -267
- package/src/services/SubsetService.ts +48 -0
- package/src/services/TM1Service.ts +127 -6
- package/src/services/index.ts +1 -1
- package/src/tests/100PercentParityCheck.test.ts +29 -8
- package/src/tests/applicationService.issue38.test.ts +293 -0
- package/src/tests/asyncOperationService.test.ts +52 -48
- package/src/tests/bugfix28.test.ts +12 -4
- package/src/tests/elementService.issue37.test.ts +571 -0
- package/src/tests/elementService.issue38.test.ts +103 -0
- package/src/tests/hierarchyService.issue38.test.ts +599 -0
- package/src/tests/processService.comprehensive.test.ts +10 -10
- package/src/tests/processService.test.ts +295 -3
- package/src/tests/restService.test.ts +1844 -139
- package/src/tests/subsetService.issue38.test.ts +182 -0
- package/src/tests/tm1Service.test.ts +95 -11
|
@@ -180,7 +180,7 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
180
180
|
}
|
|
181
181
|
const config = {};
|
|
182
182
|
if (timeout) {
|
|
183
|
-
config.timeout = timeout
|
|
183
|
+
config.timeout = timeout;
|
|
184
184
|
}
|
|
185
185
|
return await this.rest.post(url, JSON.stringify(body), config);
|
|
186
186
|
}
|
|
@@ -193,93 +193,54 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
193
193
|
const url = (0, Utils_1.formatUrl)("/Processes('{}')/tm1.Compile", processName);
|
|
194
194
|
return await this.rest.post(url, '{}');
|
|
195
195
|
}
|
|
196
|
-
async compileProcess(
|
|
197
|
-
|
|
198
|
-
/** Compile a process and return detailed compilation results
|
|
196
|
+
async compileProcess(process) {
|
|
197
|
+
/** Compile an unbound process and return syntax errors
|
|
199
198
|
*
|
|
200
|
-
* :param
|
|
201
|
-
* :return:
|
|
199
|
+
* :param process: Instance of .Process class
|
|
200
|
+
* :return: list of syntax errors (empty if successful)
|
|
202
201
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
success: true,
|
|
209
|
-
errors: []
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
return {
|
|
214
|
-
success: false,
|
|
215
|
-
errors: [response.statusText || 'Compilation failed']
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
const errorMessage = ((_c = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) || error.message || 'Unknown compilation error';
|
|
221
|
-
return {
|
|
222
|
-
success: false,
|
|
223
|
-
errors: [errorMessage]
|
|
224
|
-
};
|
|
225
|
-
}
|
|
202
|
+
const url = '/CompileProcess';
|
|
203
|
+
const payload = { Process: process.bodyAsDict };
|
|
204
|
+
const response = await this.rest.post(url, JSON.stringify(payload));
|
|
205
|
+
return response.data.value;
|
|
226
206
|
}
|
|
227
207
|
/**
|
|
228
|
-
* Poll
|
|
208
|
+
* Poll for async execution result
|
|
209
|
+
*
|
|
210
|
+
* :param asyncId: async operation ID returned from executeWithReturn with returnAsyncId=true
|
|
211
|
+
* :return: tuple of [success, status, errorLogFile] or null if not ready
|
|
229
212
|
*/
|
|
230
|
-
async pollExecuteWithReturn(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
* :return: execution result when completed
|
|
238
|
-
*/
|
|
239
|
-
// Start async execution
|
|
240
|
-
const executeUrl = (0, Utils_1.formatUrl)("/Processes('{}')/tm1.ExecuteAsync", processName);
|
|
241
|
-
const body = {};
|
|
242
|
-
if (parameters && Object.keys(parameters).length > 0) {
|
|
243
|
-
body.Parameters = Object.entries(parameters).map(([name, value]) => ({
|
|
244
|
-
Name: name,
|
|
245
|
-
Value: value
|
|
246
|
-
}));
|
|
247
|
-
}
|
|
248
|
-
const executeResponse = await this.rest.post(executeUrl, JSON.stringify(body));
|
|
249
|
-
const executionId = executeResponse.data.ID || executeResponse.data.ExecutionId;
|
|
250
|
-
if (!executionId) {
|
|
251
|
-
throw new TM1Exception_1.TM1Exception('Failed to start async process execution');
|
|
252
|
-
}
|
|
253
|
-
// Poll for completion
|
|
254
|
-
const startTime = Date.now();
|
|
255
|
-
const maxTime = timeout * 1000;
|
|
256
|
-
while (Date.now() - startTime < maxTime) {
|
|
257
|
-
try {
|
|
258
|
-
// Check execution status
|
|
259
|
-
const statusUrl = `/ExecutionStatus('${executionId}')`;
|
|
260
|
-
const statusResponse = await this.rest.get(statusUrl);
|
|
261
|
-
const status = statusResponse.data;
|
|
262
|
-
if (status.Status === 'Completed') {
|
|
263
|
-
// Get execution results
|
|
264
|
-
const resultUrl = `/ExecutionResults('${executionId}')`;
|
|
265
|
-
const resultResponse = await this.rest.get(resultUrl);
|
|
266
|
-
return resultResponse.data;
|
|
267
|
-
}
|
|
268
|
-
else if (status.Status === 'Failed') {
|
|
269
|
-
throw new TM1Exception_1.TM1Exception(`Process execution failed: ${status.ErrorMessage || 'Unknown error'}`);
|
|
270
|
-
}
|
|
271
|
-
// Wait before next poll
|
|
272
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval * 1000));
|
|
213
|
+
async pollExecuteWithReturn(asyncId) {
|
|
214
|
+
var _a, _b;
|
|
215
|
+
try {
|
|
216
|
+
const response = await this.rest.retrieve_async_response(asyncId);
|
|
217
|
+
// tm1py returns None while the async op is still in-flight (status 202).
|
|
218
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
219
|
+
return null;
|
|
273
220
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
221
|
+
// TODO: tm1py handles TM1 < v11 binary-wrapped responses via
|
|
222
|
+
// build_response_from_binary_response. Add support if needed.
|
|
223
|
+
return this._executeWithReturnParseResponse(response.data);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
// 404 means the async resource hasn't materialized yet — return null
|
|
227
|
+
// so the caller can retry. 202 means still running. Both are
|
|
228
|
+
// retryable, unlike AsyncOperationService which treats 404 as
|
|
229
|
+
// terminal FAILED for locally-tracked operations.
|
|
230
|
+
const err = error;
|
|
231
|
+
const status = (_a = err === null || err === void 0 ? void 0 : err.status) !== null && _a !== void 0 ? _a : (_b = err === null || err === void 0 ? void 0 : err.response) === null || _b === void 0 ? void 0 : _b.status;
|
|
232
|
+
if (status === 202 || status === 404) {
|
|
233
|
+
return null;
|
|
280
234
|
}
|
|
235
|
+
throw error;
|
|
281
236
|
}
|
|
282
|
-
|
|
237
|
+
}
|
|
238
|
+
_executeWithReturnParseResponse(executionSummary) {
|
|
239
|
+
var _a, _b;
|
|
240
|
+
const success = executionSummary.ProcessExecuteStatusCode === 'CompletedSuccessfully';
|
|
241
|
+
const status = executionSummary.ProcessExecuteStatusCode;
|
|
242
|
+
const errorLogFile = (_b = (_a = executionSummary.ErrorLogFile) === null || _a === void 0 ? void 0 : _a.Filename) !== null && _b !== void 0 ? _b : null;
|
|
243
|
+
return [success, status, errorLogFile];
|
|
283
244
|
}
|
|
284
245
|
async executeProcessWithReturn(processName, parameters) {
|
|
285
246
|
/** Execute process with return values
|
|
@@ -301,7 +262,7 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
301
262
|
// For now, return empty string
|
|
302
263
|
return '';
|
|
303
264
|
}
|
|
304
|
-
async
|
|
265
|
+
async debugGetBreakpoints(debugId) {
|
|
305
266
|
/** Get debug breakpoints for a debug context
|
|
306
267
|
*
|
|
307
268
|
* :param debugId: debug session ID
|
|
@@ -311,18 +272,17 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
311
272
|
const response = await this.rest.get(url);
|
|
312
273
|
return response.data.value.map((bp) => ProcessDebugBreakpoint_1.ProcessDebugBreakpoint.fromDict(bp));
|
|
313
274
|
}
|
|
314
|
-
async
|
|
315
|
-
/**
|
|
275
|
+
async debugAddBreakpoint(debugId, breakpoint) {
|
|
276
|
+
/** Add a single breakpoint to a debug context (delegates to debugAddBreakpoints)
|
|
316
277
|
*
|
|
317
278
|
* :param debugId: debug session ID
|
|
318
279
|
* :param breakpoint: ProcessDebugBreakpoint object
|
|
319
280
|
* :return: response
|
|
320
281
|
*/
|
|
321
|
-
|
|
322
|
-
return await this.rest.post(url, breakpoint.body);
|
|
282
|
+
return this.debugAddBreakpoints(debugId, [breakpoint]);
|
|
323
283
|
}
|
|
324
|
-
async
|
|
325
|
-
/**
|
|
284
|
+
async debugRemoveBreakpoint(debugId, breakpointId) {
|
|
285
|
+
/** Remove a breakpoint from a debug context
|
|
326
286
|
*
|
|
327
287
|
* :param debugId: debug session ID
|
|
328
288
|
* :param breakpointId: ID of the breakpoint
|
|
@@ -584,9 +544,11 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
584
544
|
}
|
|
585
545
|
const config = {};
|
|
586
546
|
if (timeout) {
|
|
587
|
-
config.timeout = timeout
|
|
547
|
+
config.timeout = timeout;
|
|
588
548
|
}
|
|
589
|
-
|
|
549
|
+
// rest.post returns AxiosResponse | string (string only when caller
|
|
550
|
+
// opts into returnAsyncId). debugProcess never does, so narrow.
|
|
551
|
+
const response = (await this.rest.post(url, JSON.stringify(body), config));
|
|
590
552
|
return response.data;
|
|
591
553
|
}
|
|
592
554
|
/**
|
|
@@ -676,6 +638,53 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
676
638
|
throw new TM1Exception_1.TM1Exception(`Unexpected TI return status: '${status}' for expression: '${expression}'`);
|
|
677
639
|
}
|
|
678
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* Evaluate a TI expression and return the string result.
|
|
643
|
+
*
|
|
644
|
+
* Creates a temporary process with sFunc = {formula}, compiles it,
|
|
645
|
+
* starts a debug session, adds a data breakpoint on sFunc, continues
|
|
646
|
+
* execution to evaluate, reads the result, and cleans up.
|
|
647
|
+
*/
|
|
648
|
+
async evaluateTiExpression(formula) {
|
|
649
|
+
// tm1py uses formula[formula.find("=") + 1:] which greedily strips at the
|
|
650
|
+
// first "=" anywhere in the string. We use a regex to only strip a leading
|
|
651
|
+
// "=" prefix (e.g. "=NOW;" → "NOW;"), avoiding mangling formulas with
|
|
652
|
+
// embedded "=" (e.g. comparisons like "IF(1=1,...)").
|
|
653
|
+
formula = formula.replace(/^\s*=\s*/, '');
|
|
654
|
+
// Ensure semicolon at end
|
|
655
|
+
if (!formula.trim().endsWith(';')) {
|
|
656
|
+
formula += ';';
|
|
657
|
+
}
|
|
658
|
+
const prologList = [`sFunc = ${formula}`, "sDebug='Stop';"];
|
|
659
|
+
const processName = `}TM1py${(0, uuid_1.v4)()}`;
|
|
660
|
+
const p = new Process_1.Process(processName, false, undefined, undefined, undefined, undefined, Process_1.Process.AUTO_GENERATED_STATEMENTS + prologList.join('\r\n'), '', '', '');
|
|
661
|
+
const syntaxErrors = await this.compileProcess(p);
|
|
662
|
+
if (syntaxErrors && syntaxErrors.length > 0) {
|
|
663
|
+
throw new Error(syntaxErrors.map(e => `Line ${e.LineNumber}: ${e.Message}`).join('; '));
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
await this.create(p);
|
|
667
|
+
const debugContext = await this.debugProcess(processName);
|
|
668
|
+
const debugId = debugContext.ID;
|
|
669
|
+
const breakpoint = new ProcessDebugBreakpoint_1.ProcessDebugBreakpoint(1, ProcessDebugBreakpoint_1.BreakPointType.PROCESS_DEBUG_CONTEXT_DATA_BREAK_POINT, true, ProcessDebugBreakpoint_1.HitMode.BREAK_ALWAYS, 0, '', 'sFunc');
|
|
670
|
+
await this.debugAddBreakpoint(debugId, breakpoint);
|
|
671
|
+
await this.debugContinue(debugId);
|
|
672
|
+
const result = await this.debugGetVariableValues(debugId);
|
|
673
|
+
await this.debugContinue(debugId);
|
|
674
|
+
if (!result || !('sFunc' in result)) {
|
|
675
|
+
throw new Error('unknown error: no formula result found');
|
|
676
|
+
}
|
|
677
|
+
return result['sFunc'];
|
|
678
|
+
}
|
|
679
|
+
finally {
|
|
680
|
+
try {
|
|
681
|
+
await this.delete(processName);
|
|
682
|
+
}
|
|
683
|
+
catch (_) {
|
|
684
|
+
// Cleanup failure should not mask the original error
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
679
688
|
/**
|
|
680
689
|
* Analyze process dependencies (what cubes/dimensions/processes it uses)
|
|
681
690
|
*
|
|
@@ -685,8 +694,8 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
685
694
|
* @example
|
|
686
695
|
* ```typescript
|
|
687
696
|
* const deps = await processService.analyzeProcessDependencies('ImportData');
|
|
688
|
-
*
|
|
689
|
-
*
|
|
697
|
+
* // deps.cubes => array of cube names referenced in the process
|
|
698
|
+
* // deps.dimensions => array of dimension names referenced in the process
|
|
690
699
|
* ```
|
|
691
700
|
*/
|
|
692
701
|
async analyzeProcessDependencies(processName) {
|
|
@@ -747,36 +756,34 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
747
756
|
* Validate process syntax without executing it
|
|
748
757
|
*
|
|
749
758
|
* @param processName - Name of the process
|
|
750
|
-
* @returns Promise<
|
|
759
|
+
* @returns Promise<ValidationResult> - Validation result
|
|
751
760
|
*
|
|
752
761
|
* @example
|
|
753
762
|
* ```typescript
|
|
754
763
|
* const result = await processService.validateProcessSyntax('MyProcess');
|
|
755
764
|
* if (!result.isValid) {
|
|
756
|
-
*
|
|
765
|
+
* // result.errors contains ValidationError[] entries
|
|
757
766
|
* }
|
|
758
767
|
* ```
|
|
759
768
|
*/
|
|
760
769
|
async validateProcessSyntax(processName) {
|
|
770
|
+
var _a, _b, _c;
|
|
761
771
|
try {
|
|
762
|
-
const
|
|
772
|
+
const response = await this.compile(processName);
|
|
773
|
+
if (response.status === 200) {
|
|
774
|
+
return { isValid: true, errors: [] };
|
|
775
|
+
}
|
|
763
776
|
return {
|
|
764
|
-
isValid:
|
|
765
|
-
errors:
|
|
766
|
-
line: index + 1,
|
|
767
|
-
message: error,
|
|
768
|
-
severity: 'Error'
|
|
769
|
-
}))
|
|
777
|
+
isValid: false,
|
|
778
|
+
errors: [{ line: 0, message: response.statusText || 'Compilation failed', severity: 'Error' }]
|
|
770
779
|
};
|
|
771
780
|
}
|
|
772
781
|
catch (error) {
|
|
782
|
+
const err = error;
|
|
783
|
+
const errorMessage = ((_c = (_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) || err.message || 'Validation failed';
|
|
773
784
|
return {
|
|
774
785
|
isValid: false,
|
|
775
|
-
errors: [{
|
|
776
|
-
line: 0,
|
|
777
|
-
message: error.message || 'Validation failed',
|
|
778
|
-
severity: 'Error'
|
|
779
|
-
}]
|
|
786
|
+
errors: [{ line: 0, message: errorMessage, severity: 'Error' }]
|
|
780
787
|
};
|
|
781
788
|
}
|
|
782
789
|
}
|
|
@@ -789,7 +796,7 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
789
796
|
* @example
|
|
790
797
|
* ```typescript
|
|
791
798
|
* const plan = await processService.getProcessExecutionPlan('ImportData');
|
|
792
|
-
*
|
|
799
|
+
* // plan.estimatedTime => estimated execution time (ms)
|
|
793
800
|
* ```
|
|
794
801
|
*/
|
|
795
802
|
async getProcessExecutionPlan(processName) {
|
|
@@ -864,7 +871,9 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
864
871
|
asyncOps.updateOperationStatus(operationId, AsyncOperationService_1.OperationStatus.COMPLETED, result);
|
|
865
872
|
})
|
|
866
873
|
.catch((error) => {
|
|
867
|
-
|
|
874
|
+
var _a;
|
|
875
|
+
const message = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : String(error);
|
|
876
|
+
asyncOps.updateOperationStatus(operationId, AsyncOperationService_1.OperationStatus.FAILED, undefined, message);
|
|
868
877
|
});
|
|
869
878
|
return operationId;
|
|
870
879
|
}
|
|
@@ -877,9 +886,7 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
877
886
|
* @example
|
|
878
887
|
* ```typescript
|
|
879
888
|
* const status = await processService.pollProcessExecution(operationId);
|
|
880
|
-
* if (status === OperationStatus.COMPLETED)
|
|
881
|
-
* console.log('Process completed!');
|
|
882
|
-
* }
|
|
889
|
+
* // if (status === OperationStatus.COMPLETED) handle completion
|
|
883
890
|
* ```
|
|
884
891
|
*/
|
|
885
892
|
async pollProcessExecution(operationId) {
|
|
@@ -898,7 +905,7 @@ class ProcessService extends ObjectService_1.ObjectService {
|
|
|
898
905
|
* @example
|
|
899
906
|
* ```typescript
|
|
900
907
|
* await processService.cancelProcessExecution(operationId);
|
|
901
|
-
*
|
|
908
|
+
* // cancellation is acknowledged once the promise resolves
|
|
902
909
|
* ```
|
|
903
910
|
*/
|
|
904
911
|
async cancelProcessExecution(operationId) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiosResponse, AxiosRequestConfig } from 'axios';
|
|
2
|
+
import * as https from 'https';
|
|
2
3
|
export declare enum AuthenticationMode {
|
|
3
4
|
BASIC = 1,
|
|
4
5
|
WIA = 2,
|
|
@@ -25,6 +26,9 @@ export interface RestServiceConfig {
|
|
|
25
26
|
timeout?: number;
|
|
26
27
|
cancelAtTimeout?: boolean;
|
|
27
28
|
asyncRequestsMode?: boolean;
|
|
29
|
+
asyncPollingInitialDelay?: number;
|
|
30
|
+
asyncPollingMaxDelay?: number;
|
|
31
|
+
asyncPollingBackoffFactor?: number;
|
|
28
32
|
connectionPoolSize?: number;
|
|
29
33
|
poolConnections?: number;
|
|
30
34
|
instance?: string;
|
|
@@ -36,21 +40,95 @@ export interface RestServiceConfig {
|
|
|
36
40
|
apiKey?: string;
|
|
37
41
|
accessToken?: string;
|
|
38
42
|
tenant?: string;
|
|
43
|
+
iamUrl?: string;
|
|
44
|
+
paUrl?: string;
|
|
45
|
+
cpdUrl?: string;
|
|
46
|
+
gateway?: string;
|
|
47
|
+
integratedLogin?: boolean;
|
|
48
|
+
integratedLoginDomain?: string;
|
|
49
|
+
integratedLoginService?: string;
|
|
50
|
+
integratedLoginHost?: string;
|
|
51
|
+
integratedLoginDelegate?: boolean;
|
|
52
|
+
proxies?: {
|
|
53
|
+
http?: string;
|
|
54
|
+
https?: string;
|
|
55
|
+
};
|
|
56
|
+
sslContext?: https.Agent;
|
|
57
|
+
cert?: string | [string, string];
|
|
58
|
+
reConnectOnSessionTimeout?: boolean;
|
|
59
|
+
reConnectOnRemoteDisconnect?: boolean;
|
|
60
|
+
remoteDisconnectMaxRetries?: number;
|
|
61
|
+
remoteDisconnectRetryDelay?: number;
|
|
62
|
+
remoteDisconnectMaxDelay?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface RequestOptions extends Omit<AxiosRequestConfig, 'timeout'> {
|
|
65
|
+
asyncRequestsMode?: boolean;
|
|
66
|
+
returnAsyncId?: boolean;
|
|
67
|
+
timeout?: number;
|
|
68
|
+
cancelAtTimeout?: boolean;
|
|
69
|
+
idempotent?: boolean;
|
|
70
|
+
verifyResponse?: boolean;
|
|
39
71
|
}
|
|
40
72
|
export declare class RestService {
|
|
41
73
|
private static readonly HEADERS;
|
|
42
74
|
private static readonly DEFAULT_CONNECTION_POOL_SIZE;
|
|
43
75
|
private static readonly DEFAULT_POOL_CONNECTIONS;
|
|
76
|
+
private static readonly SESSION_COOKIE_NAMES;
|
|
44
77
|
private axiosInstance;
|
|
45
78
|
private config;
|
|
46
|
-
private
|
|
79
|
+
private sessionCookies;
|
|
47
80
|
private sandboxName?;
|
|
48
81
|
private isConnected;
|
|
49
82
|
private _serverVersion?;
|
|
83
|
+
private _asyncRequestsMode;
|
|
84
|
+
private _cancelAtTimeout;
|
|
85
|
+
private _timeout;
|
|
86
|
+
private _asyncPollingInitialDelay;
|
|
87
|
+
private _asyncPollingMaxDelay;
|
|
88
|
+
private _asyncPollingBackoffFactor;
|
|
89
|
+
private _reConnectOnSessionTimeout;
|
|
90
|
+
private _reConnectOnRemoteDisconnect;
|
|
91
|
+
private _remoteDisconnectMaxRetries;
|
|
92
|
+
private _remoteDisconnectRetryDelay;
|
|
93
|
+
private _remoteDisconnectMaxDelay;
|
|
94
|
+
private _isAdmin?;
|
|
95
|
+
private _isDataAdmin?;
|
|
96
|
+
private _isSecurityAdmin?;
|
|
97
|
+
private _isOpsAdmin?;
|
|
98
|
+
private _activeUserGroupsPromise?;
|
|
50
99
|
get version(): string | undefined;
|
|
100
|
+
get isAdmin(): boolean;
|
|
101
|
+
get isDataAdmin(): boolean;
|
|
102
|
+
get isSecurityAdmin(): boolean;
|
|
103
|
+
get isOpsAdmin(): boolean;
|
|
51
104
|
constructor(config: RestServiceConfig);
|
|
105
|
+
private getSessionCookieValue;
|
|
106
|
+
private buildCookieHeader;
|
|
107
|
+
private parseSetCookieHeaders;
|
|
108
|
+
private removeAuthorizationHeader;
|
|
109
|
+
private deleteHeaderCaseInsensitive;
|
|
52
110
|
private setupAxiosInstance;
|
|
53
111
|
private buildBaseUrl;
|
|
112
|
+
/**
|
|
113
|
+
* Pick the deployment topology based on the provided config, mirroring
|
|
114
|
+
* tm1py's _determine_auth_mode + _construct_service_and_auth_root dispatch.
|
|
115
|
+
*
|
|
116
|
+
* Note: authUrl is intentionally excluded from the v12 signal set because
|
|
117
|
+
* tm1npm historically uses authUrl for CAM SSO (unlike tm1py, where auth_url
|
|
118
|
+
* is a v12-only field). apiKey is also excluded to avoid collision with the
|
|
119
|
+
* existing BASIC_API_KEY auth flow.
|
|
120
|
+
*/
|
|
121
|
+
private determineTopology;
|
|
122
|
+
/**
|
|
123
|
+
* Resolve the TM1 service root and auth root URLs for the configured topology.
|
|
124
|
+
* Mirrors tm1py's _construct_service_and_auth_root return tuple.
|
|
125
|
+
*/
|
|
126
|
+
private resolveRoots;
|
|
127
|
+
private rootsV11;
|
|
128
|
+
private rootsIbmCloud;
|
|
129
|
+
private rootsPaProxy;
|
|
130
|
+
private rootsS2s;
|
|
131
|
+
private rootsFromBaseUrl;
|
|
54
132
|
private setupInterceptors;
|
|
55
133
|
/**
|
|
56
134
|
* Determine if a request should be retried
|
|
@@ -61,42 +139,118 @@ export declare class RestService {
|
|
|
61
139
|
*/
|
|
62
140
|
private canRetryRequest;
|
|
63
141
|
/**
|
|
64
|
-
* Retry a failed request with exponential backoff
|
|
142
|
+
* Retry a failed request with exponential backoff, reconnecting the
|
|
143
|
+
* session before replay. Mirrors tm1py's _handle_remote_disconnect,
|
|
144
|
+
* which calls _manage_http_adapter() + connect() prior to retrying
|
|
145
|
+
* so a dropped session is re-established rather than replayed dead.
|
|
65
146
|
*/
|
|
66
147
|
private retryRequest;
|
|
67
148
|
private extractErrorMessage;
|
|
149
|
+
private waitTimeGenerator;
|
|
150
|
+
private _executeSyncRequest;
|
|
151
|
+
private _executeAsyncRequest;
|
|
152
|
+
private _pollAsyncResponse;
|
|
153
|
+
private _request;
|
|
68
154
|
connect(): Promise<void>;
|
|
69
155
|
disconnect(): Promise<void>;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
156
|
+
/**
|
|
157
|
+
* When `returnAsyncId: true`, the caller receives the async id string
|
|
158
|
+
* iff the server returns `202 Accepted`. If TM1 short-circuits with
|
|
159
|
+
* `200/201`, the full `AxiosResponse` is returned instead — the
|
|
160
|
+
* declared `Promise<string>` return type is a best-effort narrowing.
|
|
161
|
+
*/
|
|
162
|
+
get(url: string, options: RequestOptions & {
|
|
163
|
+
returnAsyncId: true;
|
|
164
|
+
}): Promise<string | AxiosResponse>;
|
|
165
|
+
get(url: string, options?: RequestOptions): Promise<AxiosResponse>;
|
|
166
|
+
post(url: string, data: any, options: RequestOptions & {
|
|
167
|
+
returnAsyncId: true;
|
|
168
|
+
}): Promise<string | AxiosResponse>;
|
|
169
|
+
post(url: string, data?: any, options?: RequestOptions): Promise<AxiosResponse>;
|
|
170
|
+
patch(url: string, data: any, options: RequestOptions & {
|
|
171
|
+
returnAsyncId: true;
|
|
172
|
+
}): Promise<string | AxiosResponse>;
|
|
173
|
+
patch(url: string, data?: any, options?: RequestOptions): Promise<AxiosResponse>;
|
|
174
|
+
put(url: string, data: any, options: RequestOptions & {
|
|
175
|
+
returnAsyncId: true;
|
|
176
|
+
}): Promise<string | AxiosResponse>;
|
|
177
|
+
put(url: string, data?: any, options?: RequestOptions): Promise<AxiosResponse>;
|
|
178
|
+
delete(url: string, options: RequestOptions & {
|
|
179
|
+
returnAsyncId: true;
|
|
180
|
+
}): Promise<string | AxiosResponse>;
|
|
181
|
+
delete(url: string, options?: RequestOptions): Promise<AxiosResponse>;
|
|
75
182
|
getSessionId(): string | undefined;
|
|
76
183
|
setSandbox(sandboxName?: string): void;
|
|
77
184
|
getSandbox(): string | undefined;
|
|
78
185
|
isLoggedIn(): boolean;
|
|
79
186
|
getApiMetadata(): Promise<any>;
|
|
80
187
|
/**
|
|
81
|
-
*
|
|
188
|
+
* Build an httpsAgent option that skips TLS verification when verify is false.
|
|
82
189
|
*/
|
|
83
|
-
private
|
|
190
|
+
private static insecureAgentOption;
|
|
191
|
+
/**
|
|
192
|
+
* Normalise a Set-Cookie header value (string | string[] | undefined) into a string[].
|
|
193
|
+
*/
|
|
194
|
+
private static normaliseSetCookie;
|
|
195
|
+
/**
|
|
196
|
+
* Extract a named cookie value from raw Set-Cookie headers.
|
|
197
|
+
*/
|
|
198
|
+
private static extractCookieValue;
|
|
199
|
+
/**
|
|
200
|
+
* Build Basic Authorization header.
|
|
201
|
+
* Mirrors tm1py's _build_authorization_token_basic.
|
|
202
|
+
*/
|
|
203
|
+
private static _buildAuthorizationTokenBasic;
|
|
204
|
+
/**
|
|
205
|
+
* Build CAMNamespace Authorization header.
|
|
206
|
+
* Mirrors tm1py's _build_authorization_token_cam (non-gateway path).
|
|
207
|
+
*/
|
|
208
|
+
private static _buildAuthorizationTokenCam;
|
|
84
209
|
/**
|
|
85
|
-
*
|
|
210
|
+
* Build CAMPassport Authorization token via gateway SSO.
|
|
211
|
+
* Mirrors tm1py's _build_authorization_token_cam (gateway path).
|
|
212
|
+
* Makes a GET request to the gateway URL with CAMNamespace as a query
|
|
213
|
+
* parameter and extracts the cam_passport cookie from the response.
|
|
214
|
+
*
|
|
215
|
+
* Note: tm1py uses HttpNegotiateAuth (NTLM/Kerberos) for gateway requests,
|
|
216
|
+
* which is Windows-only. This implementation sends a plain GET and relies on
|
|
217
|
+
* the gateway being accessible without NTLM. For environments requiring NTLM,
|
|
218
|
+
* pass a pre-obtained cam_passport via config.camPassport instead.
|
|
86
219
|
*/
|
|
87
|
-
private
|
|
220
|
+
private static _buildAuthorizationTokenCamSso;
|
|
88
221
|
/**
|
|
89
|
-
*
|
|
222
|
+
* Generate IBM IAM Cloud access token.
|
|
223
|
+
* Mirrors tm1py's _generate_ibm_iam_cloud_access_token.
|
|
90
224
|
*/
|
|
91
|
-
private
|
|
225
|
+
private _generateIbmIamCloudAccessToken;
|
|
92
226
|
/**
|
|
93
|
-
*
|
|
227
|
+
* Generate CPD (Cloud Pak for Data) access token.
|
|
228
|
+
* Mirrors tm1py's _generate_cpd_access_token.
|
|
94
229
|
*/
|
|
95
|
-
private
|
|
230
|
+
private _generateCpdAccessToken;
|
|
96
231
|
/**
|
|
97
|
-
*
|
|
232
|
+
* Authenticate with PA Proxy using a CPD JWT token.
|
|
233
|
+
* Mirrors tm1py's PA_PROXY flow in _start_session.
|
|
234
|
+
*/
|
|
235
|
+
private _authenticateWithPaProxy;
|
|
236
|
+
/**
|
|
237
|
+
* Authenticate Service-to-Service (v12).
|
|
238
|
+
* Mirrors tm1py's SERVICE_TO_SERVICE flow in _start_session:
|
|
239
|
+
* Uses Basic auth with applicationClientId:applicationClientSecret,
|
|
240
|
+
* then POSTs {"User": user} to the auth endpoint.
|
|
241
|
+
*/
|
|
242
|
+
private _authenticateServiceToService;
|
|
243
|
+
/**
|
|
244
|
+
* Determine the authentication mode from config.
|
|
245
|
+
* Mirrors tm1py's _determine_auth_mode, using the URL topology as the
|
|
246
|
+
* primary discriminator for v12 modes.
|
|
98
247
|
*/
|
|
99
248
|
getAuthenticationMode(): AuthenticationMode;
|
|
249
|
+
/**
|
|
250
|
+
* Set up authentication based on configuration.
|
|
251
|
+
* Mirrors tm1py's _start_session routing.
|
|
252
|
+
*/
|
|
253
|
+
private setupAuthentication;
|
|
100
254
|
/**
|
|
101
255
|
* Re-authenticate using stored configuration
|
|
102
256
|
*/
|
|
@@ -162,19 +316,27 @@ export declare class RestService {
|
|
|
162
316
|
*/
|
|
163
317
|
set_version(version: string): void;
|
|
164
318
|
/**
|
|
165
|
-
*
|
|
319
|
+
* Fetch the active user's group names as a CaseAndSpaceInsensitiveSet so
|
|
320
|
+
* membership tests are case- and whitespace-insensitive (mirrors tm1py).
|
|
321
|
+
* Concurrent callers (e.g. Promise.all([is_admin(), is_data_admin(), ...]))
|
|
322
|
+
* coalesce onto a single in-flight request.
|
|
323
|
+
*/
|
|
324
|
+
private fetchActiveUserGroupNames;
|
|
325
|
+
/**
|
|
326
|
+
* Check if current user is admin. Result is cached after the first
|
|
327
|
+
* computation, and pre-populated when the configured user is ADMIN.
|
|
166
328
|
*/
|
|
167
329
|
is_admin(): Promise<boolean>;
|
|
168
330
|
/**
|
|
169
|
-
* Check if current user is data admin
|
|
331
|
+
* Check if current user is data admin (member of Admin or DataAdmin).
|
|
170
332
|
*/
|
|
171
333
|
is_data_admin(): Promise<boolean>;
|
|
172
334
|
/**
|
|
173
|
-
* Check if current user is ops admin
|
|
335
|
+
* Check if current user is ops admin (member of Admin or OperationsAdmin).
|
|
174
336
|
*/
|
|
175
337
|
is_ops_admin(): Promise<boolean>;
|
|
176
338
|
/**
|
|
177
|
-
* Check if current user is security admin
|
|
339
|
+
* Check if current user is security admin (member of Admin or SecurityAdmin).
|
|
178
340
|
*/
|
|
179
341
|
is_security_admin(): Promise<boolean>;
|
|
180
342
|
/**
|
|
@@ -191,6 +353,23 @@ export declare class RestService {
|
|
|
191
353
|
get_http_headers(): {
|
|
192
354
|
[key: string]: string;
|
|
193
355
|
};
|
|
356
|
+
/**
|
|
357
|
+
* Insert `tm1.compact=v0` into the Accept header (after the
|
|
358
|
+
* `application/json` segment) and return the previous header value.
|
|
359
|
+
* Mirrors tm1py's add_compact_json_header.
|
|
360
|
+
*/
|
|
361
|
+
add_compact_json_header(): string;
|
|
362
|
+
/**
|
|
363
|
+
* Decode a Base64-encoded password to its UTF-8 plaintext form
|
|
364
|
+
* (mirrors tm1py's b64_decode_password).
|
|
365
|
+
*/
|
|
366
|
+
static b64_decode_password(encryptedPassword: string): string;
|
|
367
|
+
/**
|
|
368
|
+
* Coerce a boolean/number/string config value to a boolean. Strings
|
|
369
|
+
* are stripped of whitespace and lowercased before comparison with
|
|
370
|
+
* `'true'` (mirrors tm1py's translate_to_boolean).
|
|
371
|
+
*/
|
|
372
|
+
static translate_to_boolean(value: unknown): boolean;
|
|
194
373
|
/**
|
|
195
374
|
* Cancel an async operation by ID
|
|
196
375
|
*/
|
|
@@ -198,15 +377,24 @@ export declare class RestService {
|
|
|
198
377
|
/**
|
|
199
378
|
* Retrieve async operation response
|
|
200
379
|
*/
|
|
201
|
-
retrieve_async_response(async_id: string): Promise<
|
|
380
|
+
retrieve_async_response(async_id: string): Promise<AxiosResponse>;
|
|
202
381
|
/**
|
|
203
|
-
*
|
|
382
|
+
* TM1 v12 returns completed async results with HTTP 200 and encodes
|
|
383
|
+
* the true operation status in the `asyncresult` header (e.g.
|
|
384
|
+
* "500 Internal Server Error"). Mirror tm1py's
|
|
385
|
+
* `_transform_async_response` by throwing on any embedded non-2xx
|
|
386
|
+
* status so callers are not handed a 500 as "success".
|
|
204
387
|
*/
|
|
205
|
-
|
|
388
|
+
private verifyAsyncResultHeader;
|
|
206
389
|
/**
|
|
207
|
-
* Wait for async operation to complete
|
|
390
|
+
* Wait for async operation to complete using a fixed polling cadence.
|
|
391
|
+
*
|
|
392
|
+
* Unlike the internal dispatcher's {@link waitTimeGenerator} (capped
|
|
393
|
+
* exponential backoff), this public helper polls every
|
|
394
|
+
* {@link poll_interval_seconds} seconds so existing callers who tuned
|
|
395
|
+
* the cadence keep their original behavior.
|
|
208
396
|
*/
|
|
209
|
-
wait_for_async_operation(async_id: string, timeout_seconds?: number, poll_interval_seconds?: number): Promise<any>;
|
|
397
|
+
wait_for_async_operation(async_id: string, timeout_seconds?: number, poll_interval_seconds?: number, cancel_at_timeout?: boolean): Promise<any>;
|
|
210
398
|
/**
|
|
211
399
|
* Get active user name
|
|
212
400
|
*/
|