tm1npm 1.6.0 → 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.
Files changed (34) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/lib/services/ApplicationService.d.ts.map +1 -1
  3. package/lib/services/AsyncOperationService.d.ts +8 -1
  4. package/lib/services/AsyncOperationService.d.ts.map +1 -1
  5. package/lib/services/AsyncOperationService.js +69 -26
  6. package/lib/services/FileService.d.ts.map +1 -1
  7. package/lib/services/ProcessService.d.ts +18 -13
  8. package/lib/services/ProcessService.d.ts.map +1 -1
  9. package/lib/services/ProcessService.js +28 -17
  10. package/lib/services/RestService.d.ts +213 -25
  11. package/lib/services/RestService.d.ts.map +1 -1
  12. package/lib/services/RestService.js +840 -271
  13. package/lib/services/TM1Service.d.ts +42 -1
  14. package/lib/services/TM1Service.d.ts.map +1 -1
  15. package/lib/services/TM1Service.js +94 -4
  16. package/lib/tests/asyncOperationService.test.js +51 -45
  17. package/lib/tests/processService.comprehensive.test.js +2 -2
  18. package/lib/tests/processService.test.js +20 -6
  19. package/lib/tests/restService.test.d.ts +0 -4
  20. package/lib/tests/restService.test.d.ts.map +1 -1
  21. package/lib/tests/restService.test.js +1558 -143
  22. package/lib/tests/tm1Service.test.js +80 -8
  23. package/package.json +1 -1
  24. package/src/services/ApplicationService.ts +4 -4
  25. package/src/services/AsyncOperationService.ts +76 -29
  26. package/src/services/FileService.ts +3 -3
  27. package/src/services/ProcessService.ts +67 -37
  28. package/src/services/RestService.ts +1020 -278
  29. package/src/services/TM1Service.ts +124 -6
  30. package/src/tests/asyncOperationService.test.ts +52 -48
  31. package/src/tests/processService.comprehensive.test.ts +3 -3
  32. package/src/tests/processService.test.ts +21 -9
  33. package/src/tests/restService.test.ts +1844 -139
  34. package/src/tests/tm1Service.test.ts +95 -11
@@ -8,6 +8,19 @@ import { BulkService } from './BulkService';
8
8
  import { AsyncOperationService } from './AsyncOperationService';
9
9
  import { PowerBiService } from './PowerBiService';
10
10
  import { ApplicationService } from './ApplicationService';
11
+ import { AnnotationService } from './AnnotationService';
12
+ import { ChoreService } from './ChoreService';
13
+ import { GitService } from './GitService';
14
+ import { SandboxService } from './SandboxService';
15
+ import { JobService } from './JobService';
16
+ import { UserService } from './UserService';
17
+ import { ThreadService } from './ThreadService';
18
+ import { TransactionLogService } from './TransactionLogService';
19
+ import { MessageLogService } from './MessageLogService';
20
+ import { ConfigurationService } from './ConfigurationService';
21
+ import { AuditLogService } from './AuditLogService';
22
+ import { LoggerService } from './LoggerService';
23
+ import { User } from '../objects/User';
11
24
  import {
12
25
  CubeService,
13
26
  ElementService,
@@ -26,6 +39,19 @@ export class TM1Service {
26
39
  private _server?: ServerService;
27
40
  private _monitoring?: MonitoringService;
28
41
 
42
+ private _annotations?: AnnotationService;
43
+ private _chores?: ChoreService;
44
+ private _git?: GitService;
45
+ private _sandboxes?: SandboxService;
46
+ private _jobs?: JobService;
47
+ private _users?: UserService;
48
+ private _threads?: ThreadService;
49
+ private _transactionLogs?: TransactionLogService;
50
+ private _messageLogs?: MessageLogService;
51
+ private _configuration?: ConfigurationService;
52
+ private _auditLogs?: AuditLogService;
53
+ private _loggers?: LoggerService;
54
+
29
55
  public dimensions: DimensionService;
30
56
  public hierarchies: HierarchyService;
31
57
  public subsets: SubsetService;
@@ -98,9 +124,92 @@ export class TM1Service {
98
124
  return this._monitoring;
99
125
  }
100
126
 
101
- public async whoami(): Promise<string> {
102
- const user = await this.security.getCurrentUser();
103
- return user.name;
127
+ public get annotations(): AnnotationService {
128
+ if (!this._annotations) {
129
+ this._annotations = new AnnotationService(this._tm1Rest);
130
+ }
131
+ return this._annotations;
132
+ }
133
+
134
+ public get chores(): ChoreService {
135
+ if (!this._chores) {
136
+ this._chores = new ChoreService(this._tm1Rest);
137
+ }
138
+ return this._chores;
139
+ }
140
+
141
+ public get git(): GitService {
142
+ if (!this._git) {
143
+ this._git = new GitService(this._tm1Rest);
144
+ }
145
+ return this._git;
146
+ }
147
+
148
+ public get sandboxes(): SandboxService {
149
+ if (!this._sandboxes) {
150
+ this._sandboxes = new SandboxService(this._tm1Rest);
151
+ }
152
+ return this._sandboxes;
153
+ }
154
+
155
+ public get jobs(): JobService {
156
+ if (!this._jobs) {
157
+ this._jobs = new JobService(this._tm1Rest);
158
+ }
159
+ return this._jobs;
160
+ }
161
+
162
+ public get users(): UserService {
163
+ if (!this._users) {
164
+ this._users = new UserService(this._tm1Rest);
165
+ }
166
+ return this._users;
167
+ }
168
+
169
+ public get threads(): ThreadService {
170
+ if (!this._threads) {
171
+ this._threads = new ThreadService(this._tm1Rest);
172
+ }
173
+ return this._threads;
174
+ }
175
+
176
+ public get transactionLogs(): TransactionLogService {
177
+ if (!this._transactionLogs) {
178
+ this._transactionLogs = new TransactionLogService(this._tm1Rest);
179
+ }
180
+ return this._transactionLogs;
181
+ }
182
+
183
+ public get messageLogs(): MessageLogService {
184
+ if (!this._messageLogs) {
185
+ this._messageLogs = new MessageLogService(this._tm1Rest);
186
+ }
187
+ return this._messageLogs;
188
+ }
189
+
190
+ public get configuration(): ConfigurationService {
191
+ if (!this._configuration) {
192
+ this._configuration = new ConfigurationService(this._tm1Rest);
193
+ }
194
+ return this._configuration;
195
+ }
196
+
197
+ public get auditLogs(): AuditLogService {
198
+ if (!this._auditLogs) {
199
+ this._auditLogs = new AuditLogService(this._tm1Rest);
200
+ }
201
+ return this._auditLogs;
202
+ }
203
+
204
+ public get loggers(): LoggerService {
205
+ if (!this._loggers) {
206
+ this._loggers = new LoggerService(this._tm1Rest);
207
+ }
208
+ return this._loggers;
209
+ }
210
+
211
+ public async whoami(): Promise<User> {
212
+ return await this.security.getCurrentUser();
104
213
  }
105
214
 
106
215
  public async getMetadata(): Promise<any> {
@@ -108,9 +217,12 @@ export class TM1Service {
108
217
  return response.data;
109
218
  }
110
219
 
220
+ public get version(): string | undefined {
221
+ return this._tm1Rest.version;
222
+ }
223
+
111
224
  public async getVersion(): Promise<string> {
112
- const response = await this._tm1Rest.get('/Configuration/ProductVersion');
113
- return response.data.value;
225
+ return await this._tm1Rest.getVersion();
114
226
  }
115
227
 
116
228
  public get connection(): RestService {
@@ -133,6 +245,12 @@ export class TM1Service {
133
245
  return this._tm1Rest.isLoggedIn();
134
246
  }
135
247
 
248
+ /** Reconnects without teardown. Use reAuthenticate() for full disconnect+reconnect. */
249
+ public async reConnect(): Promise<void> {
250
+ await this._tm1Rest.connect();
251
+ }
252
+
253
+ /** Full teardown + reconnect. If disconnect() throws, connect() is not attempted. */
136
254
  public async reAuthenticate(): Promise<void> {
137
255
  await this._tm1Rest.disconnect();
138
256
  await this._tm1Rest.connect();
@@ -153,4 +271,4 @@ export class TM1Service {
153
271
  console.warn(`Logout failed due to exception: ${error}`);
154
272
  }
155
273
  }
156
- }
274
+ }
@@ -381,8 +381,11 @@ describe('AsyncOperationService', () => {
381
381
  });
382
382
  asyncService.updateOperationStatus(id3, OperationStatus.COMPLETED);
383
383
 
384
+ // HTTP 202 = still running on the /_async endpoint.
384
385
  mockRestService.get = jest.fn().mockResolvedValue({
385
- data: { Status: 'Running' }
386
+ status: 202,
387
+ headers: {},
388
+ data: {}
386
389
  });
387
390
 
388
391
  const activeOps = await asyncService.listActiveAsyncOperations();
@@ -641,91 +644,92 @@ describe('AsyncOperationService', () => {
641
644
  });
642
645
  });
643
646
 
647
+ // Status is inferred from the /_async('{id}') HTTP status code and the
648
+ // v12 `asyncresult` header; there is no `.Status` envelope on the new endpoint.
649
+ // These tests bypass createAsyncOperation (which tags operations as
650
+ // trackedLocally: true) to exercise the server-polling branch directly.
644
651
  describe('Server Status Mapping', () => {
645
- test('should map CompletedSuccessfully to COMPLETED', async () => {
646
- const operationId = await asyncService.createAsyncOperation({
652
+ const injectServerOperation = (id: string) => {
653
+ (asyncService as any).operations.set(id, {
654
+ id,
647
655
  type: OperationType.PROCESS_EXECUTION,
648
- name: 'TestProcess'
656
+ name: 'TestProcess',
657
+ status: OperationStatus.RUNNING,
658
+ startTime: new Date(),
659
+ trackedLocally: false
649
660
  });
661
+ };
650
662
 
651
- asyncService.updateOperationStatus(operationId, OperationStatus.RUNNING);
663
+ test('should map HTTP 200 without asyncresult header to COMPLETED', async () => {
664
+ injectServerOperation('srv-1');
652
665
 
653
666
  mockRestService.get = jest.fn().mockResolvedValue({
654
- data: { Status: 'CompletedSuccessfully', Result: { value: 42 } }
667
+ status: 200,
668
+ headers: {},
669
+ data: { value: 42 }
655
670
  });
656
671
 
657
- const status = await asyncService.getAsyncOperationStatus(operationId);
672
+ const status = await asyncService.getAsyncOperationStatus('srv-1');
658
673
  expect(status).toBe(OperationStatus.COMPLETED);
659
674
 
660
- const operation = asyncService.getOperation(operationId);
675
+ const operation = asyncService.getOperation('srv-1');
661
676
  expect(operation?.result).toEqual({ value: 42 });
662
677
  });
663
678
 
664
- test('should map CompletedWithErrors to FAILED', async () => {
665
- const operationId = await asyncService.createAsyncOperation({
666
- type: OperationType.PROCESS_EXECUTION,
667
- name: 'TestProcess'
668
- });
669
-
670
- asyncService.updateOperationStatus(operationId, OperationStatus.RUNNING);
679
+ test('should map HTTP 200 with non-2xx asyncresult header to FAILED', async () => {
680
+ injectServerOperation('srv-2');
671
681
 
672
682
  mockRestService.get = jest.fn().mockResolvedValue({
673
- data: { Status: 'CompletedWithErrors', Error: 'Process failed at line 10' }
683
+ status: 200,
684
+ headers: { asyncresult: '500 Internal Server Error' },
685
+ data: {}
674
686
  });
675
687
 
676
- const status = await asyncService.getAsyncOperationStatus(operationId);
688
+ const status = await asyncService.getAsyncOperationStatus('srv-2');
677
689
  expect(status).toBe(OperationStatus.FAILED);
678
690
 
679
- const operation = asyncService.getOperation(operationId);
680
- expect(operation?.error).toBe('Process failed at line 10');
691
+ const operation = asyncService.getOperation('srv-2');
692
+ expect(operation?.error).toBe('500 Internal Server Error');
681
693
  });
682
694
 
683
- test('should map Cancelled status correctly', async () => {
684
- const operationId = await asyncService.createAsyncOperation({
685
- type: OperationType.PROCESS_EXECUTION,
686
- name: 'TestProcess'
687
- });
695
+ test('should map thrown TM1RestException to FAILED', async () => {
696
+ injectServerOperation('srv-3');
688
697
 
689
- asyncService.updateOperationStatus(operationId, OperationStatus.RUNNING);
690
-
691
- mockRestService.get = jest.fn().mockResolvedValue({
692
- data: { Status: 'Cancelled' }
693
- });
698
+ const { TM1RestException } = require('../exceptions/TM1Exception');
699
+ mockRestService.get = jest.fn().mockRejectedValue(
700
+ new TM1RestException('Server error', 500)
701
+ );
694
702
 
695
- const status = await asyncService.getAsyncOperationStatus(operationId);
696
- expect(status).toBe(OperationStatus.CANCELLED);
703
+ const status = await asyncService.getAsyncOperationStatus('srv-3');
704
+ expect(status).toBe(OperationStatus.FAILED);
697
705
  });
698
706
 
699
- test('should map Timeout status correctly', async () => {
700
- const operationId = await asyncService.createAsyncOperation({
701
- type: OperationType.PROCESS_EXECUTION,
702
- name: 'TestProcess'
703
- });
704
-
705
- asyncService.updateOperationStatus(operationId, OperationStatus.RUNNING);
707
+ test('should map HTTP 202 to RUNNING', async () => {
708
+ injectServerOperation('srv-4');
706
709
 
707
710
  mockRestService.get = jest.fn().mockResolvedValue({
708
- data: { Status: 'Timeout' }
711
+ status: 202,
712
+ headers: {},
713
+ data: {}
709
714
  });
710
715
 
711
- const status = await asyncService.getAsyncOperationStatus(operationId);
712
- expect(status).toBe(OperationStatus.TIMEOUT);
716
+ const status = await asyncService.getAsyncOperationStatus('srv-4');
717
+ expect(status).toBe(OperationStatus.RUNNING);
713
718
  });
714
719
 
715
- test('should default to PENDING for unknown status', async () => {
720
+ test('locally-tracked operations skip server polling and return cached status', async () => {
716
721
  const operationId = await asyncService.createAsyncOperation({
717
722
  type: OperationType.PROCESS_EXECUTION,
718
- name: 'TestProcess'
723
+ name: 'LocalProcess'
719
724
  });
720
-
721
725
  asyncService.updateOperationStatus(operationId, OperationStatus.RUNNING);
722
726
 
723
- mockRestService.get = jest.fn().mockResolvedValue({
724
- data: { Status: 'UnknownStatus' }
725
- });
727
+ const getSpy = jest.fn();
728
+ mockRestService.get = getSpy;
726
729
 
727
730
  const status = await asyncService.getAsyncOperationStatus(operationId);
728
- expect(status).toBe(OperationStatus.PENDING);
731
+ expect(status).toBe(OperationStatus.RUNNING);
732
+ expect(getSpy).not.toHaveBeenCalled();
729
733
  });
730
734
  });
731
735
 
@@ -307,7 +307,7 @@ describe('ProcessService - Comprehensive Tests', () => {
307
307
  expect(mockRestService.post).toHaveBeenCalledWith(
308
308
  "/Processes('TestProcess')/tm1.ExecuteWithReturn?$expand=*",
309
309
  "{}",
310
- { timeout: 30000 }
310
+ { timeout: 30 }
311
311
  );
312
312
  });
313
313
 
@@ -940,7 +940,7 @@ describe('ProcessService - Comprehensive Tests', () => {
940
940
  expect(mockRestService.post).toHaveBeenCalledWith(
941
941
  expect.any(String),
942
942
  expect.any(String),
943
- { timeout: 60000 }
943
+ { timeout: 60 }
944
944
  );
945
945
  });
946
946
 
@@ -1244,4 +1244,4 @@ describe('ProcessService - Comprehensive Tests', () => {
1244
1244
  expect(mockRestService.get).toHaveBeenCalledTimes(3);
1245
1245
  });
1246
1246
  });
1247
- });
1247
+ });
@@ -459,9 +459,16 @@ describe('ProcessService Tests', () => {
459
459
 
460
460
  describe('Process Async Polling', () => {
461
461
  test('pollExecuteWithReturn should return parsed result on success', async () => {
462
+ // retrieve_async_response returns an AxiosResponse; the execute summary
463
+ // lives in .data. Tests that mocked the raw body directly did not
464
+ // reflect production shape and silently passed against the untyped
465
+ // parser signature.
462
466
  (mockRestService as any).retrieve_async_response = jest.fn().mockResolvedValue({
463
- ProcessExecuteStatusCode: 'CompletedSuccessfully',
464
- ErrorLogFile: null
467
+ status: 200,
468
+ data: {
469
+ ProcessExecuteStatusCode: 'CompletedSuccessfully',
470
+ ErrorLogFile: null
471
+ }
465
472
  });
466
473
 
467
474
  const result = await processService.pollExecuteWithReturn('async-001');
@@ -472,8 +479,11 @@ describe('ProcessService Tests', () => {
472
479
 
473
480
  test('pollExecuteWithReturn should return error log file when present', async () => {
474
481
  (mockRestService as any).retrieve_async_response = jest.fn().mockResolvedValue({
475
- ProcessExecuteStatusCode: 'CompletedWithMessages',
476
- ErrorLogFile: { Filename: 'TM1ProcessError_20240101.log' }
482
+ status: 200,
483
+ data: {
484
+ ProcessExecuteStatusCode: 'CompletedWithMessages',
485
+ ErrorLogFile: { Filename: 'TM1ProcessError_20240101.log' }
486
+ }
477
487
  });
478
488
 
479
489
  const result = await processService.pollExecuteWithReturn('async-002');
@@ -493,10 +503,12 @@ describe('ProcessService Tests', () => {
493
503
  });
494
504
 
495
505
  test('pollExecuteWithReturn should return null for 202 (accepted/pending)', async () => {
496
- const { TM1RestException } = require('../exceptions/TM1Exception');
497
- (mockRestService as any).retrieve_async_response = jest.fn().mockRejectedValue(
498
- new TM1RestException('Accepted', 202)
499
- );
506
+ // After the #80 refactor retrieve_async_response returns the raw AxiosResponse
507
+ // instead of throwing on 202; the pending path is now signalled by status === 202.
508
+ (mockRestService as any).retrieve_async_response = jest.fn().mockResolvedValue({
509
+ status: 202,
510
+ data: {}
511
+ });
500
512
 
501
513
  const result = await processService.pollExecuteWithReturn('async-004');
502
514
 
@@ -685,4 +697,4 @@ describe('ProcessService Tests', () => {
685
697
  expect(mockRestService.delete).toHaveBeenCalled();
686
698
  });
687
699
  });
688
- });
700
+ });