Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ecqm-bundler

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ecqm-bundler - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

QICore411eCQMs/AdultOutpatientEncountersQICore4.json

2

dist/helpers/cql.js

@@ -5,3 +5,3 @@ "use strict";

function getMainLibraryId(cql) {
const re = /library ([a-zA-Z0-9_]+)( version .*)?/g;
const re = /^library ([a-zA-Z0-9_]+)( version .*)?/gm;
const matches = re.exec(cql);

@@ -8,0 +8,0 @@ if (matches) {

@@ -15,13 +15,43 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.getELM = void 0;
exports.getELM = exports.resolveDependencies = exports.getCQLInfo = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const cql_translation_service_client_1 = require("cql-translation-service-client");
function translateCQL(paths, client) {
const cql_1 = require("./cql");
function getCQLInfo(cqlFiles) {
const info = {};
cqlFiles.forEach(f => {
const cql = fs_1.default.readFileSync(f, 'utf8');
const libraryId = (0, cql_1.getMainLibraryId)(cql);
if (libraryId) {
info[libraryId] = { cql };
}
});
return info;
}
exports.getCQLInfo = getCQLInfo;
function resolveDependencies(mainCqlContent, allCql) {
const res = [];
const re = /^include ([a-zA-Z0-9_]+)( version .*)?/gm;
let includes;
while ((includes = re.exec(mainCqlContent))) {
const libraryId = includes[1];
res.push(libraryId);
if (allCql[libraryId]) {
res.push(...resolveDependencies(allCql[libraryId].cql, allCql));
}
}
return res;
}
exports.resolveDependencies = resolveDependencies;
function translateCQL(paths, client, mainLibraryId) {
return __awaiter(this, void 0, void 0, function* () {
const cqlRequestBody = {};
paths.forEach(f => {
cqlRequestBody[path_1.default.basename(f, '.cql')] = {
cql: fs_1.default.readFileSync(f, 'utf8')
};
const cqlRequestBody = getCQLInfo(paths);
const usedDependencyIds = [
...new Set(resolveDependencies(cqlRequestBody[mainLibraryId].cql, cqlRequestBody))
];
// Only include cql files that are explicitly included by the dependency chain
Object.keys(cqlRequestBody).forEach(k => {
if (k !== mainLibraryId && !usedDependencyIds.includes(k)) {
delete cqlRequestBody[k];
}
});

@@ -44,6 +74,6 @@ const elm = yield client.convertCQL(cqlRequestBody);

}
function getELM(cqlPaths, translatorUrl) {
function getELM(cqlPaths, translatorUrl, mainLibraryId) {
return __awaiter(this, void 0, void 0, function* () {
const client = new cql_translation_service_client_1.Client(`${translatorUrl}?annotations=true&locators=true`);
const librariesOrError = yield translateCQL(cqlPaths, client);
const librariesOrError = yield translateCQL(cqlPaths, client, mainLibraryId);
if (librariesOrError instanceof Error)

@@ -50,0 +80,0 @@ throw librariesOrError;

@@ -26,5 +26,7 @@ #!/usr/bin/env node

program
.requiredOption('-c, --cql-file <path>')
.addOption(new commander_1.Option('--deps <deps...>', 'List of CQL dependency files of the main file').default([]))
.option('--deps-directory <path>', 'Directory containing all dependent CQL files')
.option('-c, --cql-file <path>')
.option('-e,--elm-file <path>')
.option('--debug', 'Enable debug mode to write contents to a ./debug directory', false)
.addOption(new commander_1.Option('--deps <deps...>', 'List of CQL or ELM dependency files of the main file').default([]))
.option('--deps-directory <path>', 'Directory containing all dependent CQL or ELM files')
.option('-n,--numer <expr>', 'Numerator expression name of measure', 'Numerator')

@@ -46,4 +48,12 @@ .option('-i,--ipop <expr>', 'Numerator expression name of measure', 'Initial Population')

const opts = program.opts();
if (opts.elmFile && opts.cqlFile) {
logger_1.default.error('ERROR: Cannot use both -c/--cql-file and -e/--elm-file\n');
program.help();
}
if (!(opts.elmFile || opts.cqlFile)) {
logger_1.default.error('ERROR: Must specify one of -c/--cql-file or -e/--elm-file\n');
program.help();
}
if (opts.deps.length !== 0 && opts.depsDirectory) {
console.error('ERROR: Must specify only one of -d/--deps and --deps-directory\n');
logger_1.default.error('ERROR: Must specify only one of -d/--deps and --deps-directory\n');
program.help();

@@ -63,35 +73,62 @@ }

.readdirSync(opts.depsDirectory)
.filter(f => path_1.default.extname(f) === '.cql' && f !== path_1.default.basename(opts.cqlFile))
.filter(f => {
if (opts.elmFile) {
return path_1.default.extname(f) === '.json' && f !== path_1.default.basename(opts.elmFile);
}
else {
return path_1.default.extname(f) === '.cql' && f !== path_1.default.basename(opts.cqlFile);
}
})
.map(f => path_1.default.join(depsBasePath, f));
}
logger_1.default.info(`Successfully gathered ${deps.length} dependencies`);
const mainCQLPath = path_1.default.resolve(opts.cqlFile);
const allCQL = [mainCQLPath, ...deps];
logger_1.default.info(`Using ${mainCQLPath} as main library`);
function main() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const mainLibraryId = (0, cql_1.getMainLibraryId)(fs_1.default.readFileSync(mainCQLPath, 'utf8'));
let elm;
try {
logger_1.default.info('Translating all CQL');
const [result, errors] = yield (0, translator_1.getELM)(allCQL, opts.translatorUrl);
if (result == null) {
logger_1.default.error('Error translating CQL:');
console.error(errors);
let mainLibraryId = null;
if (opts.elmFile) {
const mainELM = JSON.parse(fs_1.default.readFileSync(path_1.default.resolve(opts.elmFile), 'utf8'));
mainLibraryId = mainELM.library.identifier.id;
if (!mainLibraryId) {
logger_1.default.error(`Could not locate main library ID in ${opts.elmFile}`);
process.exit(1);
}
elm = result;
elm = [mainELM, ...deps.map(d => JSON.parse(fs_1.default.readFileSync(path_1.default.resolve(d), 'utf8')))];
}
catch (e) {
logger_1.default.error(`HTTP error translating CQL: ${e.message}`);
console.error(e.stack);
if ((_a = e.response) === null || _a === void 0 ? void 0 : _a.data) {
console.log(e.response.data);
else {
const mainCQLPath = path_1.default.resolve(opts.cqlFile);
const allCQL = [mainCQLPath, ...deps];
logger_1.default.info(`Using ${mainCQLPath} as main library`);
mainLibraryId = (0, cql_1.getMainLibraryId)(fs_1.default.readFileSync(mainCQLPath, 'utf8'));
if (!mainLibraryId) {
logger_1.default.error(`Could not locate main library ID in ${opts.cqlFile}`);
process.exit(1);
}
process.exit(1);
try {
logger_1.default.info('Translating all CQL');
const [result, errors] = yield (0, translator_1.getELM)(allCQL, opts.translatorUrl, mainLibraryId);
if (result == null) {
logger_1.default.error('Error translating CQL:');
console.error(errors);
process.exit(1);
}
elm = result;
}
catch (e) {
logger_1.default.error(`HTTP error translating CQL: ${e.message}`);
console.error(e.stack);
if ((_a = e.response) === null || _a === void 0 ? void 0 : _a.data) {
console.log(e.response.data);
}
process.exit(1);
}
}
if (!mainLibraryId) {
logger_1.default.error(`Could not locate main library ID in ${mainCQLPath}`);
process.exit(1);
if (opts.debug === true) {
if (!fs_1.default.existsSync('./debug')) {
fs_1.default.mkdirSync('./debug');
}
elm.forEach(e => {
fs_1.default.writeFileSync(`./debug/${e.library.identifier.id}.json`, JSON.stringify(e, null, 2), 'utf8');
});
}

@@ -98,0 +135,0 @@ const mainLibELM = (0, elm_1.findELMByIdentifier)({

{
"name": "ecqm-bundler",
"version": "0.0.1",
"version": "0.0.2",
"description": "CLI for bundling FHIR-based eCQMs",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -15,4 +15,6 @@ # ecqm-bundler

Must have an instance of the [cql-translation-service](https://github.com/cqframework/cql-translation-service) running somewhere.
For bundling with CQL files as input, you must have an instance of the [cql-translation-service](https://github.com/cqframework/cql-translation-service) running somewhere.
Bundling is also supported with JSON ELM content directly. See [Bundling from ELM Content](#bundling-from-elm-content)
## Basic Usage

@@ -32,2 +34,14 @@

**NOTE**: It is okay for the dependency directory to include more CQL files that won't be used. The bundler will parse the CQL content to identify all of the libaries
that need to be included with the translation request, and will omit any CQL files that aren't included somewhere in the dependency tree starting from the
main CQL file
### Bundling from ELM Content
``` bash
ecqm-bundler -e /path/to/main/elm/file.json --deps-directory /path/to/deps/directory -v /path/to/valueset/directory
```
This will forego the CQL translation and bundle the libraries with the ELM content provided.
## Advanced Usage/Features

@@ -70,2 +84,9 @@

### Debugging
Debug mode will write all of the ELM content to a file in the `./debug` directory, which it will create. This is useful for inspecting the contents of translated CQL before it gets
base64 encoded onto a FHIR Library resource.
To enable, use `--debug` as an option in the CLI amongst the other options
## Full List of Options

@@ -78,4 +99,6 @@

-c, --cql-file <path>
--deps <deps...> List of CQL dependency files of the main file (default: [])
--deps-directory <path> Directory containing all dependent CQL files
-e,--elm-file <path>
--debug Enable debug mode to write contents to a ./debug directory (default: false)
--deps <deps...> List of CQL or ELM dependency files of the main file (default: [])
--deps-directory <path> Directory containing all dependent CQL or ELM files
-n,--numer <expr> Numerator expression name of measure (default: "Numerator")

@@ -82,0 +105,0 @@ -i,--ipop <expr> Numerator expression name of measure (default: "Initial Population")

export function getMainLibraryId(cql: string): string | null {
const re = /library ([a-zA-Z0-9_]+)( version .*)?/g;
const re = /^library ([a-zA-Z0-9_]+)( version .*)?/gm;

@@ -4,0 +4,0 @@ const matches = re.exec(cql);

import fs from 'fs';
import path from 'path';
import translationService, { Client } from 'cql-translation-service-client';
import { getMainLibraryId } from './cql';
async function translateCQL(paths: string[], client: Client): Promise<any> {
const cqlRequestBody: translationService.CqlLibraries = {};
export function getCQLInfo(cqlFiles: string[]): translationService.CqlLibraries {
const info: translationService.CqlLibraries = {};
paths.forEach(f => {
cqlRequestBody[path.basename(f, '.cql')] = {
cql: fs.readFileSync(f, 'utf8')
};
cqlFiles.forEach(f => {
const cql = fs.readFileSync(f, 'utf8');
const libraryId = getMainLibraryId(cql);
if (libraryId) {
info[libraryId] = { cql };
}
});
return info;
}
export function resolveDependencies(
mainCqlContent: string,
allCql: translationService.CqlLibraries
): string[] {
const res: string[] = [];
const re = /^include ([a-zA-Z0-9_]+)( version .*)?/gm;
let includes;
while ((includes = re.exec(mainCqlContent))) {
const libraryId = includes[1];
res.push(libraryId);
if (allCql[libraryId]) {
res.push(...resolveDependencies(allCql[libraryId].cql, allCql));
}
}
return res;
}
async function translateCQL(paths: string[], client: Client, mainLibraryId: string): Promise<any> {
const cqlRequestBody = getCQLInfo(paths);
const usedDependencyIds = [
...new Set(resolveDependencies(cqlRequestBody[mainLibraryId].cql, cqlRequestBody))
];
// Only include cql files that are explicitly included by the dependency chain
Object.keys(cqlRequestBody).forEach(k => {
if (k !== mainLibraryId && !usedDependencyIds.includes(k)) {
delete cqlRequestBody[k];
}
});
const elm = await client.convertCQL(cqlRequestBody);

@@ -35,6 +74,7 @@ return elm;

cqlPaths: string[],
translatorUrl: string
translatorUrl: string,
mainLibraryId: string
): Promise<[any[] | null, any[] | null]> {
const client = new Client(`${translatorUrl}?annotations=true&locators=true`);
const librariesOrError = await translateCQL(cqlPaths, client);
const librariesOrError = await translateCQL(cqlPaths, client, mainLibraryId);

@@ -41,0 +81,0 @@ if (librariesOrError instanceof Error) throw librariesOrError;

@@ -24,7 +24,11 @@ #!/usr/bin/env node

program
.requiredOption('-c, --cql-file <path>')
.option('-c, --cql-file <path>')
.option('-e,--elm-file <path>')
.option('--debug', 'Enable debug mode to write contents to a ./debug directory', false)
.addOption(
new Option('--deps <deps...>', 'List of CQL dependency files of the main file').default([])
new Option('--deps <deps...>', 'List of CQL or ELM dependency files of the main file').default(
[]
)
)
.option('--deps-directory <path>', 'Directory containing all dependent CQL files')
.option('--deps-directory <path>', 'Directory containing all dependent CQL or ELM files')
.option('-n,--numer <expr>', 'Numerator expression name of measure', 'Numerator')

@@ -60,4 +64,14 @@ .option('-i,--ipop <expr>', 'Numerator expression name of measure', 'Initial Population')

if (opts.elmFile && opts.cqlFile) {
logger.error('ERROR: Cannot use both -c/--cql-file and -e/--elm-file\n');
program.help();
}
if (!(opts.elmFile || opts.cqlFile)) {
logger.error('ERROR: Must specify one of -c/--cql-file or -e/--elm-file\n');
program.help();
}
if (opts.deps.length !== 0 && opts.depsDirectory) {
console.error('ERROR: Must specify only one of -d/--deps and --deps-directory\n');
logger.error('ERROR: Must specify only one of -d/--deps and --deps-directory\n');
program.help();

@@ -82,3 +96,9 @@ }

.readdirSync(opts.depsDirectory)
.filter(f => path.extname(f) === '.cql' && f !== path.basename(opts.cqlFile))
.filter(f => {
if (opts.elmFile) {
return path.extname(f) === '.json' && f !== path.basename(opts.elmFile);
} else {
return path.extname(f) === '.cql' && f !== path.basename(opts.cqlFile);
}
})
.map(f => path.join(depsBasePath, f));

@@ -89,34 +109,62 @@ }

const mainCQLPath = path.resolve(opts.cqlFile);
const allCQL = [mainCQLPath, ...deps];
logger.info(`Using ${mainCQLPath} as main library`);
async function main(): Promise<fhir4.Bundle> {
const mainLibraryId = getMainLibraryId(fs.readFileSync(mainCQLPath, 'utf8'));
let elm: any[];
try {
logger.info('Translating all CQL');
const [result, errors] = await getELM(allCQL, opts.translatorUrl);
let mainLibraryId: string | null = null;
if (opts.elmFile) {
const mainELM = JSON.parse(fs.readFileSync(path.resolve(opts.elmFile), 'utf8'));
if (result == null) {
logger.error('Error translating CQL:');
console.error(errors);
mainLibraryId = mainELM.library.identifier.id;
if (!mainLibraryId) {
logger.error(`Could not locate main library ID in ${opts.elmFile}`);
process.exit(1);
}
elm = result;
} catch (e: any) {
logger.error(`HTTP error translating CQL: ${e.message}`);
console.error(e.stack);
if (e.response?.data) {
console.log(e.response.data);
elm = [mainELM, ...deps.map(d => JSON.parse(fs.readFileSync(path.resolve(d), 'utf8')))];
} else {
const mainCQLPath = path.resolve(opts.cqlFile);
const allCQL = [mainCQLPath, ...deps];
logger.info(`Using ${mainCQLPath} as main library`);
mainLibraryId = getMainLibraryId(fs.readFileSync(mainCQLPath, 'utf8'));
if (!mainLibraryId) {
logger.error(`Could not locate main library ID in ${opts.cqlFile}`);
process.exit(1);
}
process.exit(1);
try {
logger.info('Translating all CQL');
const [result, errors] = await getELM(allCQL, opts.translatorUrl, mainLibraryId);
if (result == null) {
logger.error('Error translating CQL:');
console.error(errors);
process.exit(1);
}
elm = result;
} catch (e: any) {
logger.error(`HTTP error translating CQL: ${e.message}`);
console.error(e.stack);
if (e.response?.data) {
console.log(e.response.data);
}
process.exit(1);
}
}
if (!mainLibraryId) {
logger.error(`Could not locate main library ID in ${mainCQLPath}`);
process.exit(1);
if (opts.debug === true) {
if (!fs.existsSync('./debug')) {
fs.mkdirSync('./debug');
}
elm.forEach(e => {
fs.writeFileSync(
`./debug/${e.library.identifier.id}.json`,
JSON.stringify(e, null, 2),
'utf8'
);
});
}

@@ -123,0 +171,0 @@

@@ -10,3 +10,3 @@ import fs from 'fs';

getELM([simpleLibraryCQL, simpleLibraryDependencyCQL], TRANSLATOR_URL)
getELM([simpleLibraryCQL, simpleLibraryDependencyCQL], TRANSLATOR_URL, 'SimpleLibrary')
.then(([elm, errors]) => {

@@ -13,0 +13,0 @@ if (elm == null || errors != null) {

@@ -14,3 +14,3 @@ import { getMainLibraryId } from '../../src/helpers/cql';

using FHIR version 4.0.1
`;
`.trim();

@@ -26,3 +26,3 @@ expect(getMainLibraryId(cql)).toEqual(libraryId);

using FHIR version 4.0.1
`;
`.trim();

@@ -29,0 +29,0 @@ expect(getMainLibraryId(cql)).toEqual(libraryId);

@@ -31,3 +31,4 @@ import path from 'path';

[SIMPLE_LIBRARY_CQL_PATH, SIMPLE_LIBRARY_DEPENDENCY_CQL_PATH],
MOCK_URL
MOCK_URL,
simpleLibraryELM.library.identifier.id
);

@@ -43,3 +44,7 @@

await expect(() =>
getELM([SIMPLE_LIBRARY_CQL_PATH, SIMPLE_LIBRARY_DEPENDENCY_CQL_PATH], MOCK_URL)
getELM(
[SIMPLE_LIBRARY_CQL_PATH, SIMPLE_LIBRARY_DEPENDENCY_CQL_PATH],
MOCK_URL,
simpleLibraryELM.library.identifier.id
)
).rejects.toThrow();

@@ -63,3 +68,7 @@ });

const [elm, errors] = await getELM([SIMPLE_LIBRARY_CQL_PATH], MOCK_URL);
const [elm, errors] = await getELM(
[SIMPLE_LIBRARY_CQL_PATH],
MOCK_URL,
simpleLibraryELM.library.identifier.id
);

@@ -66,0 +75,0 @@ expect(elm).toBeNull();

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc