dax-optimizer-sdk 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/analyze.d.ts +11 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +121 -0
- package/dist/analyze.js.map +1 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +55 -0
- package/dist/auth.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/preflight.d.ts +10 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +39 -0
- package/dist/preflight.js.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/dist/vpax-export.d.ts +13 -0
- package/dist/vpax-export.d.ts.map +1 -0
- package/dist/vpax-export.js +69 -0
- package/dist/vpax-export.js.map +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hallipr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# dax-optimizer-sdk
|
|
2
|
+
|
|
3
|
+
Programmatic DAX Optimizer analysis for Power BI and Analysis Services models. Exports a model to VPAX format, uploads it to the [DAX Optimizer](https://daxoptimizer.com) service, and returns structured performance analysis results — all from a single function call.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Three CLI tools must be installed on the machine before using this library. If any are missing, the library throws a `PrerequisiteError` with the exact install command.
|
|
8
|
+
|
|
9
|
+
| Tool | Purpose | Install |
|
|
10
|
+
|------|---------|---------|
|
|
11
|
+
| **.NET 6.0+** | Runtime for the DAX Optimizer CLI | [Download](https://dotnet.microsoft.com/download) |
|
|
12
|
+
| **DAX Optimizer CLI 1.4.7+** | Uploads VPAX and runs analysis | `dotnet tool install --global Dax.Optimizer.CLI` |
|
|
13
|
+
| **DAX Studio** | Exports models to VPAX format (`dscmd.exe`) | [Download](https://daxstudio.org/download/) or `choco install daxstudio` |
|
|
14
|
+
|
|
15
|
+
You also need a [DAX Optimizer account](https://daxoptimizer.com) with a workspace ID.
|
|
16
|
+
|
|
17
|
+
## Authentication
|
|
18
|
+
|
|
19
|
+
Three auth modes are supported. Pass an `auth` object to choose:
|
|
20
|
+
|
|
21
|
+
### PAT (Personal Access Token)
|
|
22
|
+
Non-interactive. For CI/CD and automation. Requires Enterprise license.
|
|
23
|
+
```ts
|
|
24
|
+
auth: { method: 'pat', username: 'group:automation', token: process.env.TOKEN! }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Interactive (Individual)
|
|
28
|
+
Opens a browser for sign-in. Credentials are cached locally by the CLI — subsequent runs skip the browser.
|
|
29
|
+
```ts
|
|
30
|
+
auth: { method: 'interactive' }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Group
|
|
34
|
+
Opens a browser, then switches to a shared group account. Cached like interactive.
|
|
35
|
+
```ts
|
|
36
|
+
auth: { method: 'group', username: 'my-team' }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `isInteractive(auth)` to check if a mode may open a browser (useful for showing a "waiting for login" UI). Use `logout()` to clear cached credentials.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { analyzeDaxModel } from 'dax-optimizer-sdk'
|
|
45
|
+
|
|
46
|
+
const result = await analyzeDaxModel({
|
|
47
|
+
// Authentication
|
|
48
|
+
auth: { method: 'pat', username: 'group:automation', token: process.env.TOKEN! },
|
|
49
|
+
|
|
50
|
+
// Connection to your Power BI Desktop / Analysis Services instance
|
|
51
|
+
server: 'localhost:65072',
|
|
52
|
+
databaseId: 'f97515be-bbb7-434b-83fc-b2599bf8a754',
|
|
53
|
+
|
|
54
|
+
// DAX Optimizer target
|
|
55
|
+
workspaceId: process.env.WORKSPACE_ID!,
|
|
56
|
+
modelName: 'My Model',
|
|
57
|
+
modelCreate: true, // create if it doesn't exist yet
|
|
58
|
+
contractId: '...', // required when modelCreate is true
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
console.log(`Version: ${result.version}`)
|
|
62
|
+
console.log(`Messages: ${result.messages.length}`)
|
|
63
|
+
console.log(`Rules triggered: ${result.rules.length}`)
|
|
64
|
+
console.log(`Measures analyzed: ${result.objectAnalyses.length}`)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Where do `server` and `databaseId` come from?
|
|
68
|
+
|
|
69
|
+
This library does **not** include Power BI model discovery. The caller is responsible for providing the Analysis Services connection details. Common approaches:
|
|
70
|
+
|
|
71
|
+
- **Power BI MCP Server** — use `connection_operations` → `ListLocalInstances` to discover running PBI Desktop instances, then `Connect` and `database_operations` → `List` to get the database ID.
|
|
72
|
+
- **Manual** — find the port in `%LOCALAPPDATA%\Microsoft\Power BI Desktop\AnalysisServicesWorkspaces\*\Data\msmdsrv.port.txt`.
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### `analyzeDaxModel(options): Promise<DaxOptimizerResult>`
|
|
77
|
+
|
|
78
|
+
End-to-end analysis: checks prerequisites, exports VPAX, runs analysis, returns results, cleans up temp files.
|
|
79
|
+
|
|
80
|
+
### `checkPrerequisites(): PreflightResult`
|
|
81
|
+
|
|
82
|
+
Returns `{ dotnet: boolean, daxoptimizer: boolean, dscmd: boolean }` without throwing.
|
|
83
|
+
|
|
84
|
+
### `exportVpax(options): Promise<string>`
|
|
85
|
+
|
|
86
|
+
Exports a model to VPAX format. Returns the path to the `.vpax` file.
|
|
87
|
+
|
|
88
|
+
### `runAnalysis(options): Promise<DaxOptimizerResult>`
|
|
89
|
+
|
|
90
|
+
Runs analysis on an existing VPAX file. Use this if you already have a `.vpax` and want to skip the export step.
|
|
91
|
+
|
|
92
|
+
### `logout(): Promise<void>`
|
|
93
|
+
|
|
94
|
+
Clears cached browser credentials from the DAX Optimizer CLI.
|
|
95
|
+
|
|
96
|
+
### `isInteractive(auth): boolean`
|
|
97
|
+
|
|
98
|
+
Returns `true` if the auth mode may open a browser (`interactive` or `group`).
|
|
99
|
+
|
|
100
|
+
## Error handling
|
|
101
|
+
|
|
102
|
+
| Error class | When |
|
|
103
|
+
|-------------|------|
|
|
104
|
+
| `AuthError` | Invalid auth configuration or login/logout failure |
|
|
105
|
+
| `PrerequisiteError` | A required CLI tool is missing (includes install command) |
|
|
106
|
+
| `VpaxExportError` | `dscmd.exe` failed to export the model |
|
|
107
|
+
| `AnalysisError` | `daxoptimizer` CLI failed or results couldn't be parsed |
|
|
108
|
+
|
|
109
|
+
All error classes include `stdout` / `stderr` from the failed process where available.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AnalyzeOptions, DaxOptimizerResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run DAX Optimizer analysis.
|
|
4
|
+
*
|
|
5
|
+
* Invokes: `daxoptimizer analyze <vpax> -u <user> -p <token> …`
|
|
6
|
+
* Then extracts `DaxOptimizer.json` from the output ZIP.
|
|
7
|
+
*
|
|
8
|
+
* @returns Parsed analysis result.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runAnalysis(options: AnalyzeOptions): Promise<DaxOptimizerResult>;
|
|
11
|
+
//# sourceMappingURL=analyze.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../analyze.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,cAAc,EAAiB,kBAAkB,EAAE,MAAM,YAAY,CAAA;AA8C9E;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiFhF"}
|
package/dist/analyze.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAX Optimizer – run analysis via `daxoptimizer` CLI and extract results
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { readFileSync, unlinkSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { basename, join } from 'node:path';
|
|
8
|
+
import { inflateRawSync } from 'node:zlib';
|
|
9
|
+
import { AnalysisError } from './types.js';
|
|
10
|
+
import { buildAuthArgs, isInteractive } from './auth.js';
|
|
11
|
+
// Node.js ≥ 22 ships zlib + built-in zip helpers, but for broad compat we
|
|
12
|
+
// use the `adm-zip` approach via the raw buffer. The DAX Optimizer CLI
|
|
13
|
+
// outputs a small ZIP containing `DaxOptimizer.json`. We read it with the
|
|
14
|
+
// built-in `node:zlib` + manual ZIP parsing to stay dependency-free.
|
|
15
|
+
/**
|
|
16
|
+
* Minimal ZIP reader – extracts a single named entry from a ZIP buffer.
|
|
17
|
+
* Works for the small ZIPs produced by `daxoptimizer --output`.
|
|
18
|
+
*/
|
|
19
|
+
function extractFileFromZip(zipPath, entryName) {
|
|
20
|
+
const buf = readFileSync(zipPath);
|
|
21
|
+
// Scan for local file headers (PK\x03\x04)
|
|
22
|
+
let offset = 0;
|
|
23
|
+
while (offset < buf.length - 4) {
|
|
24
|
+
if (buf[offset] === 0x50 && buf[offset + 1] === 0x4b &&
|
|
25
|
+
buf[offset + 2] === 0x03 && buf[offset + 3] === 0x04) {
|
|
26
|
+
const nameLen = buf.readUInt16LE(offset + 26);
|
|
27
|
+
const extraLen = buf.readUInt16LE(offset + 28);
|
|
28
|
+
const compressedSize = buf.readUInt32LE(offset + 18);
|
|
29
|
+
const compressionMethod = buf.readUInt16LE(offset + 8);
|
|
30
|
+
const name = buf.subarray(offset + 30, offset + 30 + nameLen).toString('utf-8');
|
|
31
|
+
const dataStart = offset + 30 + nameLen + extraLen;
|
|
32
|
+
if (name === entryName) {
|
|
33
|
+
if (compressionMethod === 0) {
|
|
34
|
+
// Stored (no compression)
|
|
35
|
+
return buf.subarray(dataStart, dataStart + compressedSize);
|
|
36
|
+
}
|
|
37
|
+
// Deflate
|
|
38
|
+
return inflateRawSync(buf.subarray(dataStart, dataStart + compressedSize));
|
|
39
|
+
}
|
|
40
|
+
offset = dataStart + compressedSize;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
offset++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new AnalysisError(`"${entryName}" not found in ZIP archive at ${zipPath}`);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run DAX Optimizer analysis.
|
|
50
|
+
*
|
|
51
|
+
* Invokes: `daxoptimizer analyze <vpax> -u <user> -p <token> …`
|
|
52
|
+
* Then extracts `DaxOptimizer.json` from the output ZIP.
|
|
53
|
+
*
|
|
54
|
+
* @returns Parsed analysis result.
|
|
55
|
+
*/
|
|
56
|
+
export function runAnalysis(options) {
|
|
57
|
+
const { vpaxPath, auth, workspaceId, region = 'westeurope', modelName, modelId, modelCreate, contractId, waitTimeout = 10, failOnIssues, } = options;
|
|
58
|
+
if (!modelId && !modelName) {
|
|
59
|
+
throw new AnalysisError('Either modelId or modelName must be provided.');
|
|
60
|
+
}
|
|
61
|
+
const stem = basename(vpaxPath, '.vpax');
|
|
62
|
+
const outputZip = join(tmpdir(), `dax_optimizer_${stem}.zip`);
|
|
63
|
+
const args = [
|
|
64
|
+
'analyze', vpaxPath,
|
|
65
|
+
...buildAuthArgs(auth),
|
|
66
|
+
'-r', region,
|
|
67
|
+
'-w', workspaceId,
|
|
68
|
+
'--wait-timeout', String(waitTimeout),
|
|
69
|
+
'--output', outputZip,
|
|
70
|
+
];
|
|
71
|
+
if (modelId) {
|
|
72
|
+
args.push('-m', modelId);
|
|
73
|
+
}
|
|
74
|
+
else if (modelName) {
|
|
75
|
+
args.push('--model-name', modelName);
|
|
76
|
+
}
|
|
77
|
+
if (modelCreate) {
|
|
78
|
+
args.push('--model-create');
|
|
79
|
+
if (contractId)
|
|
80
|
+
args.push('--contract-id', contractId);
|
|
81
|
+
}
|
|
82
|
+
if (failOnIssues) {
|
|
83
|
+
args.push('--fail-on-issues');
|
|
84
|
+
}
|
|
85
|
+
// Interactive auth may need extra time for browser login
|
|
86
|
+
const authBuffer = isInteractive(auth) ? 5 : 2;
|
|
87
|
+
const timeoutMs = (waitTimeout + authBuffer) * 60_000;
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
execFile('daxoptimizer', args, { timeout: timeoutMs }, (error, stdout, stderr) => {
|
|
90
|
+
// Print CLI output for visibility
|
|
91
|
+
if (stdout)
|
|
92
|
+
console.log(stdout);
|
|
93
|
+
if (stderr)
|
|
94
|
+
console.error(stderr);
|
|
95
|
+
if (error) {
|
|
96
|
+
const detail = (stderr || stdout || '').trim().split('\n').slice(-5).join('\n');
|
|
97
|
+
const msg = error.code === 'ENOENT'
|
|
98
|
+
? 'daxoptimizer CLI not found. Install it with: dotnet tool install --global Dax.Optimizer.CLI'
|
|
99
|
+
: `DAX Optimizer CLI failed (code ${error.code}): ${detail}`;
|
|
100
|
+
return reject(new AnalysisError(msg, typeof error.code === 'number' ? error.code : undefined, stdout, stderr));
|
|
101
|
+
}
|
|
102
|
+
// Extract result JSON from output ZIP
|
|
103
|
+
try {
|
|
104
|
+
const jsonBuf = extractFileFromZip(outputZip, 'DaxOptimizer.json');
|
|
105
|
+
// Strip UTF-8 BOM if present
|
|
106
|
+
const jsonStr = jsonBuf.toString('utf-8').replace(/^\uFEFF/, '');
|
|
107
|
+
const result = JSON.parse(jsonStr);
|
|
108
|
+
// Cleanup temp zip
|
|
109
|
+
try {
|
|
110
|
+
unlinkSync(outputZip);
|
|
111
|
+
}
|
|
112
|
+
catch { /* ignore */ }
|
|
113
|
+
resolve(result);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
reject(e instanceof AnalysisError ? e : new AnalysisError(`Failed to extract results: ${e.message}`));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../analyze.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DaxOptimizerAuth } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build CLI auth flags for the given credential.
|
|
4
|
+
*
|
|
5
|
+
* - `pat` → `['-u', username, '-p', token]`
|
|
6
|
+
* - `interactive` → `[]` (CLI opens browser)
|
|
7
|
+
* - `group` → `['-u', username]` (CLI opens browser, switches to group)
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildAuthArgs(auth: DaxOptimizerAuth): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Whether this auth mode may open a browser and require user interaction.
|
|
12
|
+
* Useful for the host app to show a "waiting for login" UI or adjust timeouts.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isInteractive(auth: DaxOptimizerAuth): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Clear cached browser credentials from the DAX Optimizer CLI.
|
|
17
|
+
* Runs: `daxoptimizer logout`
|
|
18
|
+
*/
|
|
19
|
+
export declare function logout(): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../auth.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAa,MAAM,YAAY,CAAA;AAExD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAc9D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAE7D;AAED;;;GAGG;AACH,wBAAgB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAatC"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAX Optimizer – authentication helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { AuthError } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Build CLI auth flags for the given credential.
|
|
8
|
+
*
|
|
9
|
+
* - `pat` → `['-u', username, '-p', token]`
|
|
10
|
+
* - `interactive` → `[]` (CLI opens browser)
|
|
11
|
+
* - `group` → `['-u', username]` (CLI opens browser, switches to group)
|
|
12
|
+
*/
|
|
13
|
+
export function buildAuthArgs(auth) {
|
|
14
|
+
switch (auth.method) {
|
|
15
|
+
case 'pat':
|
|
16
|
+
if (!auth.username)
|
|
17
|
+
throw new AuthError('PAT auth requires a username.');
|
|
18
|
+
if (!auth.token)
|
|
19
|
+
throw new AuthError('PAT auth requires a token.');
|
|
20
|
+
return ['-u', auth.username, '-p', auth.token];
|
|
21
|
+
case 'group':
|
|
22
|
+
if (!auth.username)
|
|
23
|
+
throw new AuthError('Group auth requires a username.');
|
|
24
|
+
return ['-u', auth.username];
|
|
25
|
+
case 'interactive':
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether this auth mode may open a browser and require user interaction.
|
|
31
|
+
* Useful for the host app to show a "waiting for login" UI or adjust timeouts.
|
|
32
|
+
*/
|
|
33
|
+
export function isInteractive(auth) {
|
|
34
|
+
return auth.method === 'interactive' || auth.method === 'group';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clear cached browser credentials from the DAX Optimizer CLI.
|
|
38
|
+
* Runs: `daxoptimizer logout`
|
|
39
|
+
*/
|
|
40
|
+
export function logout() {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
execFile('daxoptimizer', ['logout'], { timeout: 15_000 }, (error, stdout, stderr) => {
|
|
43
|
+
if (stdout)
|
|
44
|
+
console.log(stdout);
|
|
45
|
+
if (error) {
|
|
46
|
+
const msg = error.code === 'ENOENT'
|
|
47
|
+
? 'daxoptimizer CLI not found. Install it with: dotnet tool install --global Dax.Optimizer.CLI'
|
|
48
|
+
: `Logout failed: ${(stderr || stdout || error.message).trim()}`;
|
|
49
|
+
return reject(new AuthError(msg));
|
|
50
|
+
}
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../auth.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yCAAyC;AACzC,8EAA8E;AAE9E,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAoB,SAAS,EAAE,MAAM,YAAY,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAsB;IAClD,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,KAAK;YACR,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,SAAS,CAAC,+BAA+B,CAAC,CAAA;YACxE,IAAI,CAAC,IAAI,CAAC,KAAK;gBAAK,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAA;YACrE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAEhD,KAAK,OAAO;YACV,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,SAAS,CAAC,iCAAiC,CAAC,CAAA;YAC1E,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAE9B,KAAK,aAAa;YAChB,OAAO,EAAE,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAsB;IAClD,OAAO,IAAI,CAAC,MAAM,KAAK,aAAa,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,CAAA;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM;IACpB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YAClF,IAAI,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC/B,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,GAAG,GAAI,KAA+B,CAAC,IAAI,KAAK,QAAQ;oBAC5D,CAAC,CAAC,6FAA6F;oBAC/F,CAAC,CAAC,kBAAkB,CAAC,MAAM,IAAI,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;gBAClE,OAAO,MAAM,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;YACnC,CAAC;YACD,OAAO,EAAE,CAAA;QACX,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DaxOptimizerOptions, DaxOptimizerResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* End-to-end DAX Optimizer analysis.
|
|
4
|
+
*
|
|
5
|
+
* 1. Validates prerequisites (dscmd, daxoptimizer, dotnet).
|
|
6
|
+
* 2. Exports the model to a temporary VPAX file.
|
|
7
|
+
* 3. Uploads VPAX to DAX Optimizer and waits for results.
|
|
8
|
+
* 4. Cleans up temp files and returns the parsed result.
|
|
9
|
+
*/
|
|
10
|
+
export declare function analyzeDaxModel(options: DaxOptimizerOptions): Promise<DaxOptimizerResult>;
|
|
11
|
+
export { checkPrerequisites } from './preflight.js';
|
|
12
|
+
export { exportVpax, findDscmd } from './vpax-export.js';
|
|
13
|
+
export { runAnalysis } from './analyze.js';
|
|
14
|
+
export { logout, isInteractive, buildAuthArgs } from './auth.js';
|
|
15
|
+
export * from './types.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAwBA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,YAAY,CAAA;AAKnB;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoC/F;AAGD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAChE,cAAc,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAX Optimizer – public API
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// Usage:
|
|
6
|
+
// import { analyzeDaxModel, checkPrerequisites, logout } from 'dax-optimizer-sdk'
|
|
7
|
+
//
|
|
8
|
+
// // PAT auth (CI/CD)
|
|
9
|
+
// const result = await analyzeDaxModel({
|
|
10
|
+
// auth: { method: 'pat', username: 'group:automation', token: '...' },
|
|
11
|
+
// server: 'localhost:65072',
|
|
12
|
+
// databaseId: '...',
|
|
13
|
+
// workspaceId: '...',
|
|
14
|
+
// modelName: 'My Model',
|
|
15
|
+
// })
|
|
16
|
+
//
|
|
17
|
+
// // Interactive auth (local dev)
|
|
18
|
+
// const result2 = await analyzeDaxModel({
|
|
19
|
+
// auth: { method: 'interactive' },
|
|
20
|
+
// ...
|
|
21
|
+
// })
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
import { unlinkSync } from 'node:fs';
|
|
24
|
+
import { PrerequisiteError, } from './types.js';
|
|
25
|
+
import { checkPrerequisites } from './preflight.js';
|
|
26
|
+
import { exportVpax } from './vpax-export.js';
|
|
27
|
+
import { runAnalysis } from './analyze.js';
|
|
28
|
+
/**
|
|
29
|
+
* End-to-end DAX Optimizer analysis.
|
|
30
|
+
*
|
|
31
|
+
* 1. Validates prerequisites (dscmd, daxoptimizer, dotnet).
|
|
32
|
+
* 2. Exports the model to a temporary VPAX file.
|
|
33
|
+
* 3. Uploads VPAX to DAX Optimizer and waits for results.
|
|
34
|
+
* 4. Cleans up temp files and returns the parsed result.
|
|
35
|
+
*/
|
|
36
|
+
export async function analyzeDaxModel(options) {
|
|
37
|
+
// ── 1. Preflight ──────────────────────────────────────────────────────
|
|
38
|
+
const pre = checkPrerequisites();
|
|
39
|
+
const missing = [];
|
|
40
|
+
if (!pre.dotnet)
|
|
41
|
+
missing.push('dotnet 6.0+');
|
|
42
|
+
if (!pre.daxoptimizer)
|
|
43
|
+
missing.push('daxoptimizer CLI (dotnet tool install --global Dax.Optimizer.CLI)');
|
|
44
|
+
if (!pre.dscmd)
|
|
45
|
+
missing.push('dscmd.exe (DAX Studio)');
|
|
46
|
+
if (missing.length)
|
|
47
|
+
throw new PrerequisiteError(missing);
|
|
48
|
+
// ── 2. Export VPAX ────────────────────────────────────────────────────
|
|
49
|
+
const vpaxPath = await exportVpax({
|
|
50
|
+
databaseId: options.databaseId,
|
|
51
|
+
server: options.server,
|
|
52
|
+
connectionString: options.connectionString,
|
|
53
|
+
});
|
|
54
|
+
// ── 3. Run analysis ───────────────────────────────────────────────────
|
|
55
|
+
try {
|
|
56
|
+
const result = await runAnalysis({
|
|
57
|
+
vpaxPath,
|
|
58
|
+
auth: options.auth,
|
|
59
|
+
workspaceId: options.workspaceId,
|
|
60
|
+
region: options.region ?? 'westeurope',
|
|
61
|
+
modelName: options.modelName ?? options.databaseId,
|
|
62
|
+
modelId: options.modelId,
|
|
63
|
+
modelCreate: options.modelCreate,
|
|
64
|
+
contractId: options.contractId,
|
|
65
|
+
waitTimeout: options.waitTimeout,
|
|
66
|
+
failOnIssues: options.failOnIssues,
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
// ── 4. Cleanup temp VPAX ────────────────────────────────────────────
|
|
72
|
+
try {
|
|
73
|
+
unlinkSync(vpaxPath);
|
|
74
|
+
}
|
|
75
|
+
catch { /* ignore */ }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Re-export everything so callers can import from a single entry point
|
|
79
|
+
export { checkPrerequisites } from './preflight.js';
|
|
80
|
+
export { exportVpax, findDscmd } from './vpax-export.js';
|
|
81
|
+
export { runAnalysis } from './analyze.js';
|
|
82
|
+
export { logout, isInteractive, buildAuthArgs } from './auth.js';
|
|
83
|
+
export * from './types.js';
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAC9E,EAAE;AACF,SAAS;AACT,oFAAoF;AACpF,EAAE;AACF,wBAAwB;AACxB,2CAA2C;AAC3C,2EAA2E;AAC3E,iCAAiC;AACjC,yBAAyB;AACzB,0BAA0B;AAC1B,6BAA6B;AAC7B,OAAO;AACP,EAAE;AACF,oCAAoC;AACpC,4CAA4C;AAC5C,uCAAuC;AACvC,UAAU;AACV,OAAO;AACP,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAGL,iBAAiB,GAClB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAA4B;IAChE,yEAAyE;IACzE,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAA;IAChC,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,IAAI,CAAC,GAAG,CAAC,MAAM;QAAQ,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAClD,IAAI,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAA;IACxG,IAAI,CAAC,GAAG,CAAC,KAAK;QAAS,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAA;IAC7D,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAA;IAExD,yEAAyE;IACzE,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC;QAChC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;KAC3C,CAAC,CAAA;IAEF,yEAAyE;IACzE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;YAC/B,QAAQ;YACR,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,YAAY;YACtC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,UAAU;YAClD,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,OAAO,CAAC,YAAY;SACnC,CAAC,CAAA;QAEF,OAAO,MAAM,CAAA;IACf,CAAC;YAAS,CAAC;QACT,uEAAuE;QACvE,IAAI,CAAC;YAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAChE,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PreflightResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether all required CLI tools are available.
|
|
4
|
+
*
|
|
5
|
+
* - `dotnet` 6.0+
|
|
6
|
+
* - `daxoptimizer` (Dax.Optimizer.CLI .NET global tool)
|
|
7
|
+
* - `dscmd.exe` (DAX Studio CLI)
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkPrerequisites(): PreflightResult;
|
|
10
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../preflight.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAsB5C;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAX Optimizer – prerequisite checks
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { findDscmd } from './vpax-export.js';
|
|
6
|
+
function commandExists(cmd, args = ['--version']) {
|
|
7
|
+
try {
|
|
8
|
+
execFileSync(cmd, args, { encoding: 'utf-8', timeout: 10_000, stdio: 'pipe' });
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function checkDotnet() {
|
|
16
|
+
try {
|
|
17
|
+
const out = execFileSync('dotnet', ['--version'], { encoding: 'utf-8', timeout: 10_000, stdio: 'pipe' });
|
|
18
|
+
const major = parseInt(out.trim().split('.')[0], 10);
|
|
19
|
+
return major >= 6;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check whether all required CLI tools are available.
|
|
27
|
+
*
|
|
28
|
+
* - `dotnet` 6.0+
|
|
29
|
+
* - `daxoptimizer` (Dax.Optimizer.CLI .NET global tool)
|
|
30
|
+
* - `dscmd.exe` (DAX Studio CLI)
|
|
31
|
+
*/
|
|
32
|
+
export function checkPrerequisites() {
|
|
33
|
+
return {
|
|
34
|
+
dotnet: checkDotnet(),
|
|
35
|
+
daxoptimizer: commandExists('daxoptimizer'),
|
|
36
|
+
dscmd: findDscmd() !== null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../preflight.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sCAAsC;AACtC,8EAA8E;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C,SAAS,aAAa,CAAC,GAAW,EAAE,OAAiB,CAAC,WAAW,CAAC;IAChE,IAAI,CAAC;QACH,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC9E,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACxG,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QACpD,OAAO,KAAK,IAAI,CAAC,CAAA;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,MAAM,EAAE,WAAW,EAAE;QACrB,YAAY,EAAE,aAAa,CAAC,cAAc,CAAC;QAC3C,KAAK,EAAE,SAAS,EAAE,KAAK,IAAI;KAC5B,CAAA;AACH,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** Region endpoints supported by DAX Optimizer */
|
|
2
|
+
export type DaxOptimizerRegion = 'australiaeast' | 'eastus' | 'westeurope';
|
|
3
|
+
/** PAT auth — non-interactive, for CI/CD and automation (requires Enterprise license). */
|
|
4
|
+
export interface PatAuth {
|
|
5
|
+
method: 'pat';
|
|
6
|
+
/** DAX Optimizer group account name */
|
|
7
|
+
username: string;
|
|
8
|
+
/** Personal access token */
|
|
9
|
+
token: string;
|
|
10
|
+
}
|
|
11
|
+
/** Interactive auth — opens browser for individual sign-in, credentials cached by CLI. */
|
|
12
|
+
export interface InteractiveAuth {
|
|
13
|
+
method: 'interactive';
|
|
14
|
+
}
|
|
15
|
+
/** Group auth — opens browser, then switches to specified group account. */
|
|
16
|
+
export interface GroupAuth {
|
|
17
|
+
method: 'group';
|
|
18
|
+
/** DAX Optimizer group account name */
|
|
19
|
+
username: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Authentication credential for DAX Optimizer.
|
|
23
|
+
*
|
|
24
|
+
* - `pat`: Non-interactive. App stores and passes username + token.
|
|
25
|
+
* - `interactive`: Browser login. CLI caches credentials locally.
|
|
26
|
+
* - `group`: Browser login + group switch. CLI caches credentials locally.
|
|
27
|
+
*/
|
|
28
|
+
export type DaxOptimizerAuth = PatAuth | InteractiveAuth | GroupAuth;
|
|
29
|
+
/** Options for `analyzeDaxModel()` – everything the caller must supply. */
|
|
30
|
+
export interface DaxOptimizerOptions {
|
|
31
|
+
/** Analysis Services server address, e.g. `localhost:65072` */
|
|
32
|
+
server: string;
|
|
33
|
+
/** Database / catalog ID on that server */
|
|
34
|
+
databaseId: string;
|
|
35
|
+
/** Authentication credential */
|
|
36
|
+
auth: DaxOptimizerAuth;
|
|
37
|
+
/** DAX Optimizer workspace ID */
|
|
38
|
+
workspaceId: string;
|
|
39
|
+
/** Workspace region (default: `westeurope`) */
|
|
40
|
+
region?: DaxOptimizerRegion;
|
|
41
|
+
/** Human-readable model name inside DAX Optimizer (default: databaseId) */
|
|
42
|
+
modelName?: string;
|
|
43
|
+
/** Existing model ID – use instead of modelName to target an exact model */
|
|
44
|
+
modelId?: string;
|
|
45
|
+
/** Create the model in DAX Optimizer if it doesn't exist yet */
|
|
46
|
+
modelCreate?: boolean;
|
|
47
|
+
/** Contract ID – required when `modelCreate` is true */
|
|
48
|
+
contractId?: string;
|
|
49
|
+
/** Max minutes to wait for analysis completion (default: 10) */
|
|
50
|
+
waitTimeout?: number;
|
|
51
|
+
/** Exit with error when critical issues are found */
|
|
52
|
+
failOnIssues?: boolean;
|
|
53
|
+
/** Optional: full MSOLAP connection string (overrides server + databaseId) */
|
|
54
|
+
connectionString?: string;
|
|
55
|
+
}
|
|
56
|
+
/** Subset of options needed just for VPAX export. */
|
|
57
|
+
export interface VpaxExportOptions {
|
|
58
|
+
databaseId: string;
|
|
59
|
+
server: string;
|
|
60
|
+
connectionString?: string;
|
|
61
|
+
outputPath?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Subset of options needed for the `daxoptimizer analyze` step. */
|
|
64
|
+
export interface AnalyzeOptions {
|
|
65
|
+
vpaxPath: string;
|
|
66
|
+
auth: DaxOptimizerAuth;
|
|
67
|
+
workspaceId: string;
|
|
68
|
+
region: DaxOptimizerRegion;
|
|
69
|
+
modelName?: string;
|
|
70
|
+
modelId?: string;
|
|
71
|
+
modelCreate?: boolean;
|
|
72
|
+
contractId?: string;
|
|
73
|
+
waitTimeout?: number;
|
|
74
|
+
failOnIssues?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface PreflightResult {
|
|
77
|
+
dotnet: boolean;
|
|
78
|
+
daxoptimizer: boolean;
|
|
79
|
+
dscmd: boolean;
|
|
80
|
+
}
|
|
81
|
+
export interface DaxOptimizerMessage {
|
|
82
|
+
category: number;
|
|
83
|
+
level: number;
|
|
84
|
+
text: string;
|
|
85
|
+
objectType: number;
|
|
86
|
+
objectName: string;
|
|
87
|
+
tableName?: string;
|
|
88
|
+
}
|
|
89
|
+
export interface RuleLocalization {
|
|
90
|
+
localeId: string;
|
|
91
|
+
title: string;
|
|
92
|
+
description: string;
|
|
93
|
+
}
|
|
94
|
+
export interface DaxOptimizerRule {
|
|
95
|
+
docId: string;
|
|
96
|
+
url: string;
|
|
97
|
+
localizations: RuleLocalization[];
|
|
98
|
+
}
|
|
99
|
+
export interface Recommendation {
|
|
100
|
+
ruleDocId: string;
|
|
101
|
+
weight: number;
|
|
102
|
+
cpuWeight: number;
|
|
103
|
+
materializationWeight: number;
|
|
104
|
+
fingerprint: string;
|
|
105
|
+
relatedNodes?: {
|
|
106
|
+
type: number;
|
|
107
|
+
from: number;
|
|
108
|
+
to: number;
|
|
109
|
+
}[];
|
|
110
|
+
}
|
|
111
|
+
export interface RecommendationNode {
|
|
112
|
+
type: number;
|
|
113
|
+
from: number;
|
|
114
|
+
to: number;
|
|
115
|
+
name: string;
|
|
116
|
+
tableName?: string;
|
|
117
|
+
recommendations: Recommendation[];
|
|
118
|
+
}
|
|
119
|
+
export interface MeasureReference {
|
|
120
|
+
type: number;
|
|
121
|
+
from: number;
|
|
122
|
+
to: number;
|
|
123
|
+
name: string;
|
|
124
|
+
tableName: string;
|
|
125
|
+
}
|
|
126
|
+
export interface ObjectAnalysis {
|
|
127
|
+
objectName: string;
|
|
128
|
+
objectType: number;
|
|
129
|
+
dax: string;
|
|
130
|
+
daxFingerprint: string;
|
|
131
|
+
weight: number;
|
|
132
|
+
cpuScore: number;
|
|
133
|
+
cpuOptimizability: number;
|
|
134
|
+
materializationScore: number;
|
|
135
|
+
materializationOptimizability: number;
|
|
136
|
+
directReferenceCount: number;
|
|
137
|
+
indirectReferenceCount: number;
|
|
138
|
+
totalExecutionCount: number;
|
|
139
|
+
maximumExecutionCount: number;
|
|
140
|
+
recommendationCount: number;
|
|
141
|
+
recommendationNodes: RecommendationNode[];
|
|
142
|
+
referencedMeasures: MeasureReference[];
|
|
143
|
+
syntaxTokens: unknown[];
|
|
144
|
+
syntaxTree: unknown;
|
|
145
|
+
}
|
|
146
|
+
export interface DaxOptimizerResult {
|
|
147
|
+
version: string;
|
|
148
|
+
messages: DaxOptimizerMessage[];
|
|
149
|
+
rules: DaxOptimizerRule[];
|
|
150
|
+
objectAnalyses: ObjectAnalysis[];
|
|
151
|
+
}
|
|
152
|
+
export declare class PrerequisiteError extends Error {
|
|
153
|
+
missing: string[];
|
|
154
|
+
constructor(missing: string[], message?: string);
|
|
155
|
+
}
|
|
156
|
+
export declare class AuthError extends Error {
|
|
157
|
+
constructor(message: string);
|
|
158
|
+
}
|
|
159
|
+
export declare class VpaxExportError extends Error {
|
|
160
|
+
stdout?: string | undefined;
|
|
161
|
+
stderr?: string | undefined;
|
|
162
|
+
constructor(message: string, stdout?: string | undefined, stderr?: string | undefined);
|
|
163
|
+
}
|
|
164
|
+
export declare class AnalysisError extends Error {
|
|
165
|
+
exitCode?: number | undefined;
|
|
166
|
+
stdout?: string | undefined;
|
|
167
|
+
stderr?: string | undefined;
|
|
168
|
+
constructor(message: string, exitCode?: number | undefined, stdout?: string | undefined, stderr?: string | undefined);
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAX Optimizer – shared types
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// ── Errors ──────────────────────────────────────────────────────────────────
|
|
5
|
+
export class PrerequisiteError extends Error {
|
|
6
|
+
missing;
|
|
7
|
+
constructor(missing, message) {
|
|
8
|
+
super(message ?? `Missing prerequisites: ${missing.join(', ')}`);
|
|
9
|
+
this.missing = missing;
|
|
10
|
+
this.name = 'PrerequisiteError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class AuthError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'AuthError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class VpaxExportError extends Error {
|
|
20
|
+
stdout;
|
|
21
|
+
stderr;
|
|
22
|
+
constructor(message, stdout, stderr) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.stdout = stdout;
|
|
25
|
+
this.stderr = stderr;
|
|
26
|
+
this.name = 'VpaxExportError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class AnalysisError extends Error {
|
|
30
|
+
exitCode;
|
|
31
|
+
stdout;
|
|
32
|
+
stderr;
|
|
33
|
+
constructor(message, exitCode, stdout, stderr) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.exitCode = exitCode;
|
|
36
|
+
this.stdout = stdout;
|
|
37
|
+
this.stderr = stderr;
|
|
38
|
+
this.name = 'AnalysisError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AA4K9E,+EAA+E;AAE/E,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACvB;IAAnB,YAAmB,OAAiB,EAAE,OAAgB;QACpD,KAAK,CAAC,OAAO,IAAI,0BAA0B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAD/C,YAAO,GAAP,OAAO,CAAU;QAElC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF;AAED,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;IACzB,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACJ;IAAwB;IAA5D,YAAY,OAAe,EAAS,MAAe,EAAS,MAAe;QACzE,KAAK,CAAC,OAAO,CAAC,CAAA;QADoB,WAAM,GAAN,MAAM,CAAS;QAAS,WAAM,GAAN,MAAM,CAAS;QAEzE,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,aAAc,SAAQ,KAAK;IACF;IAA0B;IAAwB;IAAtF,YAAY,OAAe,EAAS,QAAiB,EAAS,MAAe,EAAS,MAAe;QACnG,KAAK,CAAC,OAAO,CAAC,CAAA;QADoB,aAAQ,GAAR,QAAQ,CAAS;QAAS,WAAM,GAAN,MAAM,CAAS;QAAS,WAAM,GAAN,MAAM,CAAS;QAEnG,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VpaxExportOptions } from './types.js';
|
|
2
|
+
/** Locate dscmd.exe on PATH or at well-known install locations. */
|
|
3
|
+
export declare function findDscmd(): string | null;
|
|
4
|
+
/**
|
|
5
|
+
* Export a Power BI / Analysis Services model to VPAX format.
|
|
6
|
+
*
|
|
7
|
+
* Runs: `dscmd.exe vpax <outputPath> -s <server> -d <databaseId>`
|
|
8
|
+
* or: `dscmd.exe vpax <outputPath> -c <connectionString>`
|
|
9
|
+
*
|
|
10
|
+
* @returns Absolute path to the exported .vpax file.
|
|
11
|
+
*/
|
|
12
|
+
export declare function exportVpax(options: VpaxExportOptions): Promise<string>;
|
|
13
|
+
//# sourceMappingURL=vpax-export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpax-export.d.ts","sourceRoot":"","sources":["../vpax-export.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,iBAAiB,EAAmB,MAAM,YAAY,CAAA;AAM/D,mEAAmE;AACnE,wBAAgB,SAAS,IAAI,MAAM,GAAG,IAAI,CAYzC;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CtE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// DAX Optimizer – VPAX export via DAX Studio CLI (dscmd.exe)
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
5
|
+
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { VpaxExportError } from './types.js';
|
|
9
|
+
const KNOWN_DSCMD_PATHS = [
|
|
10
|
+
'C:\\Program Files\\DAX Studio\\dscmd.exe',
|
|
11
|
+
];
|
|
12
|
+
/** Locate dscmd.exe on PATH or at well-known install locations. */
|
|
13
|
+
export function findDscmd() {
|
|
14
|
+
// Check PATH via `where` on Windows
|
|
15
|
+
try {
|
|
16
|
+
const result = execFileSync('where', ['dscmd.exe'], { encoding: 'utf-8', timeout: 5_000 });
|
|
17
|
+
const first = result.trim().split(/\r?\n/)[0];
|
|
18
|
+
if (first && existsSync(first))
|
|
19
|
+
return first;
|
|
20
|
+
}
|
|
21
|
+
catch { /* not on PATH */ }
|
|
22
|
+
for (const p of KNOWN_DSCMD_PATHS) {
|
|
23
|
+
if (existsSync(p))
|
|
24
|
+
return p;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Export a Power BI / Analysis Services model to VPAX format.
|
|
30
|
+
*
|
|
31
|
+
* Runs: `dscmd.exe vpax <outputPath> -s <server> -d <databaseId>`
|
|
32
|
+
* or: `dscmd.exe vpax <outputPath> -c <connectionString>`
|
|
33
|
+
*
|
|
34
|
+
* @returns Absolute path to the exported .vpax file.
|
|
35
|
+
*/
|
|
36
|
+
export function exportVpax(options) {
|
|
37
|
+
const { databaseId, server, connectionString } = options;
|
|
38
|
+
const outputPath = options.outputPath ?? join(tmpdir(), `${databaseId}.vpax`);
|
|
39
|
+
const dscmd = findDscmd();
|
|
40
|
+
if (!dscmd) {
|
|
41
|
+
throw new VpaxExportError('dscmd.exe not found. Install DAX Studio from https://daxstudio.org/download/ or add it to PATH.');
|
|
42
|
+
}
|
|
43
|
+
// Ensure output directory exists
|
|
44
|
+
const dir = dirname(outputPath);
|
|
45
|
+
if (!existsSync(dir))
|
|
46
|
+
mkdirSync(dir, { recursive: true });
|
|
47
|
+
// Build command arguments
|
|
48
|
+
const args = ['vpax', outputPath];
|
|
49
|
+
if (connectionString) {
|
|
50
|
+
args.push('-c', connectionString);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
args.push('-s', server, '-d', databaseId);
|
|
54
|
+
}
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
execFile(dscmd, args, { timeout: 120_000 }, (error, stdout, stderr) => {
|
|
57
|
+
if (error) {
|
|
58
|
+
return reject(new VpaxExportError(`VPAX export failed: ${error.message}`, stdout, stderr));
|
|
59
|
+
}
|
|
60
|
+
if (!existsSync(outputPath)) {
|
|
61
|
+
return reject(new VpaxExportError('VPAX export completed but output file was not created.', stdout, stderr));
|
|
62
|
+
}
|
|
63
|
+
const sizeMb = (statSync(outputPath).size / (1024 * 1024)).toFixed(2);
|
|
64
|
+
console.log(`VPAX exported (${sizeMb} MB): ${outputPath}`);
|
|
65
|
+
resolve(outputPath);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=vpax-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vpax-export.js","sourceRoot":"","sources":["../vpax-export.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dax-optimizer-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Programmatic DAX Optimizer analysis for Power BI and Analysis Services models",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"preflight": "node dist/preflight.js"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"power-bi",
|
|
29
|
+
"dax",
|
|
30
|
+
"dax-optimizer",
|
|
31
|
+
"analysis-services",
|
|
32
|
+
"vpax",
|
|
33
|
+
"performance",
|
|
34
|
+
"semantic-model"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/hallipr/dax-optimizer"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^25.6.0",
|
|
43
|
+
"typescript": "^6.0.2"
|
|
44
|
+
}
|
|
45
|
+
}
|