tm1npm 1.2.0 → 1.3.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.
@@ -0,0 +1,399 @@
1
+ import { ObjectService } from './ObjectService';
2
+ import { RestService } from './RestService';
3
+ import { formatUrl } from '../utils/Utils';
4
+
5
+ /**
6
+ * Debug session information
7
+ */
8
+ export interface DebugSession {
9
+ id: string;
10
+ processName: string;
11
+ status: 'Running' | 'Paused' | 'Completed' | 'Failed';
12
+ currentLine?: number;
13
+ variables?: { [key: string]: any };
14
+ }
15
+
16
+ /**
17
+ * Process variable information
18
+ */
19
+ export interface ProcessVariable {
20
+ name: string;
21
+ value: any;
22
+ type: 'String' | 'Numeric' | 'Unknown';
23
+ scope: 'Local' | 'Global' | 'Parameter';
24
+ }
25
+
26
+ /**
27
+ * Call stack frame
28
+ */
29
+ export interface CallStackFrame {
30
+ procedureName: string;
31
+ lineNumber: number;
32
+ source?: string;
33
+ }
34
+
35
+ /**
36
+ * Process validation result
37
+ */
38
+ export interface ValidationResult {
39
+ isValid: boolean;
40
+ errors: Array<{
41
+ line: number;
42
+ message: string;
43
+ severity: 'Error' | 'Warning';
44
+ }>;
45
+ }
46
+
47
+ /**
48
+ * DebuggerService - Comprehensive TM1 process debugging service
49
+ *
50
+ * Provides advanced debugging capabilities including session management,
51
+ * variable inspection, expression evaluation, and call stack analysis.
52
+ */
53
+ export class DebuggerService extends ObjectService {
54
+ private activeSessions: Map<string, DebugSession> = new Map();
55
+
56
+ constructor(rest: RestService) {
57
+ super(rest);
58
+ }
59
+
60
+ /**
61
+ * Create a new debug session for a process
62
+ *
63
+ * @param processName - Name of the process to debug
64
+ * @returns Promise<string> - Debug session ID
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const sessionId = await debuggerService.createDebugSession('MyProcess');
69
+ * ```
70
+ */
71
+ public async createDebugSession(processName: string): Promise<string> {
72
+ const url = formatUrl("/Processes('{}')/tm1.BeginDebug", processName);
73
+ const response = await this.rest.post(url, {});
74
+
75
+ const sessionId = response.data.ID || response.data.SessionId || this.generateSessionId();
76
+
77
+ // Track the session
78
+ this.activeSessions.set(sessionId, {
79
+ id: sessionId,
80
+ processName,
81
+ status: 'Paused',
82
+ currentLine: 0
83
+ });
84
+
85
+ return sessionId;
86
+ }
87
+
88
+ /**
89
+ * Terminate an active debug session
90
+ *
91
+ * @param sessionId - Debug session ID
92
+ * @returns Promise<void>
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * await debuggerService.terminateDebugSession(sessionId);
97
+ * ```
98
+ */
99
+ public async terminateDebugSession(sessionId: string): Promise<void> {
100
+ const session = this.activeSessions.get(sessionId);
101
+ if (!session) {
102
+ throw new Error(`Debug session ${sessionId} not found`);
103
+ }
104
+
105
+ const url = formatUrl("/Processes('{}')/tm1.EndDebug", session.processName);
106
+
107
+ try {
108
+ await this.rest.post(url, {});
109
+ } finally {
110
+ this.activeSessions.delete(sessionId);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Step into the next line (enter function calls)
116
+ *
117
+ * @param sessionId - Debug session ID
118
+ * @returns Promise<void>
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * await debuggerService.stepInto(sessionId);
123
+ * ```
124
+ */
125
+ public async stepInto(sessionId: string): Promise<void> {
126
+ const session = this.activeSessions.get(sessionId);
127
+ if (!session) {
128
+ throw new Error(`Debug session ${sessionId} not found`);
129
+ }
130
+
131
+ const url = formatUrl("/Processes('{}')/tm1.DebugStepIn", session.processName);
132
+ await this.rest.post(url, {});
133
+
134
+ session.status = 'Paused';
135
+ if (session.currentLine !== undefined) {
136
+ session.currentLine++;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Step over the next line (don't enter function calls)
142
+ *
143
+ * @param sessionId - Debug session ID
144
+ * @returns Promise<void>
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * await debuggerService.stepOver(sessionId);
149
+ * ```
150
+ */
151
+ public async stepOver(sessionId: string): Promise<void> {
152
+ const session = this.activeSessions.get(sessionId);
153
+ if (!session) {
154
+ throw new Error(`Debug session ${sessionId} not found`);
155
+ }
156
+
157
+ const url = formatUrl("/Processes('{}')/tm1.DebugStepOver", session.processName);
158
+ await this.rest.post(url, {});
159
+
160
+ session.status = 'Paused';
161
+ if (session.currentLine !== undefined) {
162
+ session.currentLine++;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Step out of current function
168
+ *
169
+ * @param sessionId - Debug session ID
170
+ * @returns Promise<void>
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * await debuggerService.stepOut(sessionId);
175
+ * ```
176
+ */
177
+ public async stepOut(sessionId: string): Promise<void> {
178
+ const session = this.activeSessions.get(sessionId);
179
+ if (!session) {
180
+ throw new Error(`Debug session ${sessionId} not found`);
181
+ }
182
+
183
+ const url = formatUrl("/Processes('{}')/tm1.DebugStepOut", session.processName);
184
+ await this.rest.post(url, {});
185
+
186
+ session.status = 'Paused';
187
+ }
188
+
189
+ /**
190
+ * Continue execution until next breakpoint or completion
191
+ *
192
+ * @param sessionId - Debug session ID
193
+ * @returns Promise<void>
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * await debuggerService.continueExecution(sessionId);
198
+ * ```
199
+ */
200
+ public async continueExecution(sessionId: string): Promise<void> {
201
+ const session = this.activeSessions.get(sessionId);
202
+ if (!session) {
203
+ throw new Error(`Debug session ${sessionId} not found`);
204
+ }
205
+
206
+ const url = formatUrl("/Processes('{}')/tm1.DebugContinue", session.processName);
207
+ await this.rest.post(url, {});
208
+
209
+ session.status = 'Running';
210
+ }
211
+
212
+ /**
213
+ * Get all process variables in current debug session
214
+ *
215
+ * @param sessionId - Debug session ID
216
+ * @returns Promise<ProcessVariable[]> - Array of process variables
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const variables = await debuggerService.getProcessVariables(sessionId);
221
+ * console.log(variables);
222
+ * ```
223
+ */
224
+ public async getProcessVariables(sessionId: string): Promise<ProcessVariable[]> {
225
+ const session = this.activeSessions.get(sessionId);
226
+ if (!session) {
227
+ throw new Error(`Debug session ${sessionId} not found`);
228
+ }
229
+
230
+ const url = formatUrl("/Processes('{}')/Variables", session.processName);
231
+ const response = await this.rest.get(url);
232
+
233
+ const variables: ProcessVariable[] = [];
234
+
235
+ if (response.data.value) {
236
+ for (const variable of response.data.value) {
237
+ variables.push({
238
+ name: variable.Name,
239
+ value: variable.Value !== undefined ? variable.Value : null,
240
+ type: variable.Type || 'Unknown',
241
+ scope: variable.Scope || 'Local'
242
+ });
243
+ }
244
+ }
245
+
246
+ // Update session cache
247
+ session.variables = variables.reduce((acc, v) => {
248
+ acc[v.name] = v.value;
249
+ return acc;
250
+ }, {} as { [key: string]: any });
251
+
252
+ return variables;
253
+ }
254
+
255
+ /**
256
+ * Set a process variable value during debugging
257
+ *
258
+ * @param sessionId - Debug session ID
259
+ * @param variable - Variable name
260
+ * @param value - New value
261
+ * @returns Promise<void>
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * await debuggerService.setProcessVariable(sessionId, 'vCounter', 10);
266
+ * ```
267
+ */
268
+ public async setProcessVariable(
269
+ sessionId: string,
270
+ variable: string,
271
+ value: any
272
+ ): Promise<void> {
273
+ const session = this.activeSessions.get(sessionId);
274
+ if (!session) {
275
+ throw new Error(`Debug session ${sessionId} not found`);
276
+ }
277
+
278
+ const url = formatUrl("/Processes('{}')/Variables('{}')", session.processName, variable);
279
+ await this.rest.patch(url, { Value: value });
280
+
281
+ // Update session cache
282
+ if (session.variables) {
283
+ session.variables[variable] = value;
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Evaluate a TM1 expression in the debug context
289
+ *
290
+ * @param sessionId - Debug session ID
291
+ * @param expression - TM1 expression to evaluate
292
+ * @returns Promise<any> - Evaluation result
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * const result = await debuggerService.evaluateExpression(
297
+ * sessionId,
298
+ * 'vCounter + 10'
299
+ * );
300
+ * console.log('Result:', result);
301
+ * ```
302
+ */
303
+ public async evaluateExpression(sessionId: string, expression: string): Promise<any> {
304
+ const session = this.activeSessions.get(sessionId);
305
+ if (!session) {
306
+ throw new Error(`Debug session ${sessionId} not found`);
307
+ }
308
+
309
+ const url = formatUrl("/Processes('{}')/tm1.Evaluate", session.processName);
310
+ const response = await this.rest.post(url, { Expression: expression });
311
+
312
+ return response.data.Result !== undefined ? response.data.Result : response.data.value;
313
+ }
314
+
315
+ /**
316
+ * Get the current call stack
317
+ *
318
+ * @param sessionId - Debug session ID
319
+ * @returns Promise<CallStackFrame[]> - Call stack frames
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const callStack = await debuggerService.getCallStack(sessionId);
324
+ * callStack.forEach(frame => {
325
+ * console.log(`${frame.procedureName} at line ${frame.lineNumber}`);
326
+ * });
327
+ * ```
328
+ */
329
+ public async getCallStack(sessionId: string): Promise<CallStackFrame[]> {
330
+ const session = this.activeSessions.get(sessionId);
331
+ if (!session) {
332
+ throw new Error(`Debug session ${sessionId} not found`);
333
+ }
334
+
335
+ const url = formatUrl("/Processes('{}')/tm1.GetCallStack", session.processName);
336
+
337
+ try {
338
+ const response = await this.rest.get(url);
339
+
340
+ if (response.data.value) {
341
+ return response.data.value.map((frame: any) => ({
342
+ procedureName: frame.ProcedureName || frame.Name,
343
+ lineNumber: frame.LineNumber || 0,
344
+ source: frame.Source
345
+ }));
346
+ }
347
+ } catch (error: any) {
348
+ // If call stack API not available, return basic info
349
+ if (error.response?.status === 404) {
350
+ return [{
351
+ procedureName: session.processName,
352
+ lineNumber: session.currentLine || 0
353
+ }];
354
+ }
355
+ throw error;
356
+ }
357
+
358
+ return [];
359
+ }
360
+
361
+ /**
362
+ * Get debug session info
363
+ *
364
+ * @param sessionId - Debug session ID
365
+ * @returns DebugSession | undefined - Session information
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * const session = debuggerService.getSessionInfo(sessionId);
370
+ * console.log('Status:', session?.status);
371
+ * ```
372
+ */
373
+ public getSessionInfo(sessionId: string): DebugSession | undefined {
374
+ return this.activeSessions.get(sessionId);
375
+ }
376
+
377
+ /**
378
+ * List all active debug sessions
379
+ *
380
+ * @returns DebugSession[] - Array of active sessions
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * const sessions = debuggerService.listActiveSessions();
385
+ * console.log(`${sessions.length} active sessions`);
386
+ * ```
387
+ */
388
+ public listActiveSessions(): DebugSession[] {
389
+ return Array.from(this.activeSessions.values());
390
+ }
391
+
392
+ /**
393
+ * Generate a unique session ID
394
+ * @private
395
+ */
396
+ private generateSessionId(): string {
397
+ return `debug_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
398
+ }
399
+ }
@@ -686,4 +686,166 @@ export class ProcessService extends ObjectService {
686
686
  throw error;
687
687
  }
688
688
  }
689
+
690
+ /**
691
+ * Analyze process dependencies (what cubes/dimensions/processes it uses)
692
+ *
693
+ * @param processName - Name of the process
694
+ * @returns Promise<any> - Dependency information
695
+ *
696
+ * @example
697
+ * ```typescript
698
+ * const deps = await processService.analyzeProcessDependencies('ImportData');
699
+ * console.log('Cubes used:', deps.cubes);
700
+ * console.log('Dimensions used:', deps.dimensions);
701
+ * ```
702
+ */
703
+ public async analyzeProcessDependencies(processName: string): Promise<any> {
704
+ // Get process code
705
+ const process = await this.get(processName);
706
+
707
+ const dependencies = {
708
+ cubes: [] as string[],
709
+ dimensions: [] as string[],
710
+ processes: [] as string[],
711
+ subsets: [] as string[]
712
+ };
713
+
714
+ // Extract all code sections
715
+ const allCode = [
716
+ process.prologProcedure || '',
717
+ process.metadataProcedure || '',
718
+ process.dataProcedure || '',
719
+ process.epilogProcedure || ''
720
+ ].join('\n');
721
+
722
+ // Cube references (CellGetN, CellPutN, CubeExists, etc.)
723
+ const cubeMatches = allCode.match(/(?:CellGetN|CellPutN|CubeExists|CubeClear|CubeCreate|CubeDestroy)\s*\(\s*['"]([^'"]+)['"]/gi);
724
+ if (cubeMatches) {
725
+ cubeMatches.forEach(match => {
726
+ const cubeMatch = match.match(/['"]([^'"]+)['"]/);
727
+ if (cubeMatch && cubeMatch[1]) {
728
+ if (!dependencies.cubes.includes(cubeMatch[1])) {
729
+ dependencies.cubes.push(cubeMatch[1]);
730
+ }
731
+ }
732
+ });
733
+ }
734
+
735
+ // Dimension references
736
+ const dimMatches = allCode.match(/(?:DimensionCreate|DimensionExists|DimIx)\s*\(\s*['"]([^'"]+)['"]/gi);
737
+ if (dimMatches) {
738
+ dimMatches.forEach(match => {
739
+ const dimMatch = match.match(/['"]([^'"]+)['"]/);
740
+ if (dimMatch && dimMatch[1]) {
741
+ if (!dependencies.dimensions.includes(dimMatch[1])) {
742
+ dependencies.dimensions.push(dimMatch[1]);
743
+ }
744
+ }
745
+ });
746
+ }
747
+
748
+ // Process references (ExecuteProcess)
749
+ const processMatches = allCode.match(/ExecuteProcess\s*\(\s*['"]([^'"]+)['"]/gi);
750
+ if (processMatches) {
751
+ processMatches.forEach(match => {
752
+ const procMatch = match.match(/['"]([^'"]+)['"]/);
753
+ if (procMatch && procMatch[1]) {
754
+ if (!dependencies.processes.includes(procMatch[1])) {
755
+ dependencies.processes.push(procMatch[1]);
756
+ }
757
+ }
758
+ });
759
+ }
760
+
761
+ return dependencies;
762
+ }
763
+
764
+ /**
765
+ * Validate process syntax without executing it
766
+ *
767
+ * @param processName - Name of the process
768
+ * @returns Promise<{isValid: boolean, errors: any[]}> - Validation result
769
+ *
770
+ * @example
771
+ * ```typescript
772
+ * const result = await processService.validateProcessSyntax('MyProcess');
773
+ * if (!result.isValid) {
774
+ * console.error('Errors:', result.errors);
775
+ * }
776
+ * ```
777
+ */
778
+ public async validateProcessSyntax(processName: string): Promise<{isValid: boolean, errors: any[]}> {
779
+ try {
780
+ const result = await this.compileProcess(processName);
781
+ return {
782
+ isValid: result.success,
783
+ errors: result.errors.map((error, index) => ({
784
+ line: index + 1,
785
+ message: error,
786
+ severity: 'Error'
787
+ }))
788
+ };
789
+ } catch (error: any) {
790
+ return {
791
+ isValid: false,
792
+ errors: [{
793
+ line: 0,
794
+ message: error.message || 'Validation failed',
795
+ severity: 'Error'
796
+ }]
797
+ };
798
+ }
799
+ }
800
+
801
+ /**
802
+ * Get process execution plan (estimated resource usage)
803
+ *
804
+ * @param processName - Name of the process
805
+ * @returns Promise<any> - Execution plan information
806
+ *
807
+ * @example
808
+ * ```typescript
809
+ * const plan = await processService.getProcessExecutionPlan('ImportData');
810
+ * console.log('Estimated execution time:', plan.estimatedTime);
811
+ * ```
812
+ */
813
+ public async getProcessExecutionPlan(processName: string): Promise<any> {
814
+ const process = await this.get(processName);
815
+
816
+ // Analyze process characteristics
817
+ const plan = {
818
+ processName: process.name,
819
+ hasDataSource: !!(process as any).dataSource,
820
+ dataSourceType: (process as any).dataSource?.type || 'None',
821
+ hasParameters: process.parameters && process.parameters.length > 0,
822
+ parameterCount: process.parameters ? process.parameters.length : 0,
823
+ hasVariables: process.variables && process.variables.length > 0,
824
+ variableCount: process.variables ? process.variables.length : 0,
825
+ procedures: {
826
+ hasPrologProcedure: !!(process.prologProcedure && process.prologProcedure.trim()),
827
+ hasMetadataProcedure: !!(process.metadataProcedure && process.metadataProcedure.trim()),
828
+ hasDataProcedure: !!(process.dataProcedure && process.dataProcedure.trim()),
829
+ hasEpilogProcedure: !!(process.epilogProcedure && process.epilogProcedure.trim())
830
+ },
831
+ estimatedComplexity: 'Unknown'
832
+ };
833
+
834
+ // Estimate complexity
835
+ const totalCodeLength =
836
+ (process.prologProcedure?.length || 0) +
837
+ (process.metadataProcedure?.length || 0) +
838
+ (process.dataProcedure?.length || 0) +
839
+ (process.epilogProcedure?.length || 0);
840
+
841
+ if (totalCodeLength < 500) {
842
+ plan.estimatedComplexity = 'Low';
843
+ } else if (totalCodeLength < 2000) {
844
+ plan.estimatedComplexity = 'Medium';
845
+ } else {
846
+ plan.estimatedComplexity = 'High';
847
+ }
848
+
849
+ return plan;
850
+ }
689
851
  }
@@ -3,6 +3,7 @@ import { DimensionService } from './DimensionService';
3
3
  import { HierarchyService } from './HierarchyService';
4
4
  import { SubsetService } from './SubsetService';
5
5
  import { DataFrameService } from './DataFrameService';
6
+ import { DebuggerService } from './DebuggerService';
6
7
  import {
7
8
  CubeService,
8
9
  ElementService,
@@ -33,6 +34,7 @@ export class TM1Service {
33
34
  public files: FileService;
34
35
  public sessions: SessionService;
35
36
  public dataframes: DataFrameService;
37
+ public debugger: DebuggerService;
36
38
 
37
39
  constructor(config: RestServiceConfig) {
38
40
  this._tm1Rest = new RestService(config);
@@ -50,6 +52,7 @@ export class TM1Service {
50
52
  this.files = new FileService(this._tm1Rest);
51
53
  this.sessions = new SessionService(this._tm1Rest);
52
54
  this.dataframes = new DataFrameService(this._tm1Rest);
55
+ this.debugger = new DebuggerService(this._tm1Rest);
53
56
  }
54
57
 
55
58
  public async connect(): Promise<void> {
@@ -11,6 +11,7 @@ export { ConfigurationService } from './ConfigurationService';
11
11
  export { ServerService } from './ServerService';
12
12
  export { MonitoringService } from './MonitoringService';
13
13
  export { DataFrameService } from './DataFrameService';
14
+ export { DebuggerService } from './DebuggerService';
14
15
 
15
16
  // Additional services
16
17
  export { AnnotationService } from './AnnotationService';