Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial typescript setup #260

Merged
merged 42 commits into from Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fac0cad
WIP: rename files for later additions
Jan 24, 2022
dc1c2fe
Initial working ts commit
Jan 24, 2022
745ba3d
Fix luxon types to v1.x; add self to contributors
Jan 24, 2022
db290bf
Update examples and fix optional param
Jan 24, 2022
4de4fa4
remove babel config
Jan 24, 2022
b327c44
Update cql4Browsers
Jan 24, 2022
c2c0e2d
Yarn audit fix
Jan 25, 2022
ea41f12
Switch parseInt calls to Number.isInteger
Feb 1, 2022
f509a08
Yarn audit fix
Feb 1, 2022
0822989
yarn --> npm
Feb 1, 2022
a69f154
Update grep of validate_cql4browsers to include file extension
Feb 1, 2022
e45194c
Update CI to npm over yarn
Feb 1, 2022
e9eb611
Type and infra improvements
Feb 3, 2022
ef6b8b5
Add default exports
Feb 3, 2022
c45764b
Fix overloaded import since Length class moved
Feb 3, 2022
507c41f
Simplify usage of accessing built in Math functions
Feb 11, 2022
4d8e43d
Apply suggestions from code review
mgramigna Feb 15, 2022
d6828e2
Quick updates from code review
Feb 15, 2022
505e7a5
Missed an optional version
Feb 15, 2022
9db6020
Add stricter type to datetime functions
Feb 15, 2022
304540e
Unify luxon imports in datetime
Feb 15, 2022
b215c5a
Add interfaces specifying CodeService and Data Provider structure
Feb 15, 2022
13834cb
Used access type modifiers in select constructors
Feb 15, 2022
bdb31d7
Regen cql4browsers and add npm build as prereq to browserify
Feb 15, 2022
5785caa
Better type support
Feb 16, 2022
cff80a5
Remove redundant constructor assignments
Feb 16, 2022
7ce2fa8
move to inline exports
Feb 16, 2022
5f2b60b
Update findValueSet to use consistent type when not found
Feb 16, 2022
305b66c
Update structure of exposed API
Feb 16, 2022
4d0fdab
Rename runtime-types -> runtime.types
Feb 16, 2022
6e8ad76
update cql4browsers
Feb 16, 2022
e273e0d
Add comment explaining overrides
Feb 16, 2022
d5d5340
Added TODO for future refactors of datetime
Feb 16, 2022
ce8fc0d
Reworked unit conversion to use clearer type checking
Feb 16, 2022
8e97dd8
removed patients from DataProvider
Feb 16, 2022
a221bd6
added abstract base class for Date and DateTime
Feb 17, 2022
fc4f98b
Add more proper typing
Feb 17, 2022
73dd291
Make Concept default codes to [] when null
cmoesel Feb 18, 2022
5c93ba8
Make ValueSet default codes to [] when null
Feb 21, 2022
3f2b51f
Properly mark code as type string in messageListeners.ts
Feb 21, 2022
aeb97fb
Update docs
Feb 21, 2022
544f7dc
A fitting end to this work: a typo
Feb 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 11 additions & 2 deletions .eslintrc.json
@@ -1,4 +1,5 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
Expand All @@ -7,6 +8,8 @@
}
},
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"indent": ["error", 2, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"no-console": ["warn", { "allow": ["warn", "error"] }],
Expand All @@ -15,10 +18,16 @@
"quotes": ["error", "single", { "allowTemplateLiterals": true, "avoidEscape": true }],
"semi": ["error", "always"]
},
"overrides": [{
"files": ["test/**/*.{js,ts}", "examples/**/*.js", "bin/**/*.js"],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}],
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": ["eslint:recommended", "prettier"]
}
"extends": ["plugin:@typescript-eslint/recommended", "prettier"]
}
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Expand Up @@ -11,9 +11,9 @@ It is strongly recommended to include a person from each of those projects as a
- [ ] Tests are included and test edge cases
- [ ] Tests have been run locally and pass
- [ ] Code coverage has not gone down and all code touched or added is covered.
- [ ] Code passes lint and prettier (hint: use `yarn run test:plus` to run tests, lint, and prettier)
- [ ] Code passes lint and prettier (hint: use `npm run test:plus` to run tests, lint, and prettier)
- [ ] All dependent libraries are appropriately updated or have a corresponding PR related to this change
- [ ] `cql4browsers.js` built with `yarn run build:browserify` if source changed.
- [ ] `cql4browsers.js` built with `npm run build:browserify` if source changed.

**Reviewer:**

Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci-workflow.yml
Expand Up @@ -4,14 +4,14 @@ on: [push, pull_request]

jobs:
audit:
name: Check yarn audit
name: Check npm audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- run: yarn audit
- run: npm audit
env:
CI: true
cql4browsers:
Expand All @@ -33,9 +33,9 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '12.x'
- run: yarn install
- run: yarn run lint
- run: yarn run prettier
- run: npm install
- run: npm run lint
- run: npm run prettier
env:
CI: true
test:
Expand All @@ -51,8 +51,8 @@ jobs:
with:
timezone: America/New_York
- run: date +"%Y-%m-%d %T"
- run: yarn install
- run: npm install
- run: ./bin/check_for_nonassertive_tests.sh
- run: npx nyc --reporter=lcov yarn test && npx codecov
- run: npx nyc --reporter=lcov npm test && npx codecov
mgramigna marked this conversation as resolved.
Show resolved Hide resolved
env:
CI: true
4 changes: 4 additions & 0 deletions .mocharc.json
@@ -0,0 +1,4 @@
{
"extension": ["ts"],
"require": "ts-node/register"
}
24 changes: 14 additions & 10 deletions OVERVIEW.md
Expand Up @@ -9,6 +9,7 @@ Technologies
------------

The CQL execution framework was originally written in [CoffeeScript](http://coffeescript.org/). CoffeeScript is a scripting language that compiles down to JavaScript. In 2020, the CQL Execution framework source code was migrated from CoffeeScript to ES6 JavaScript. JavaScript execution code allows the reference implementation to be integrated into a variety of environments, including servers, other languages’ runtime environments, and standard web browsers.
In 2022, the CQL execution framework was converted to [TypeScript](https://www.typescriptlang.org/) to make use of static typing and support both TypeScript and JavaScript users of the framework.

The CQL execution framework tests and examples are configured to run using [Node.js](http://nodejs.org/), but can be easily integrated into other JavaScript runtime environments.

Expand Down Expand Up @@ -46,7 +47,7 @@ The expression 1 + 2 is represented in JSON ELM as follows:

### ELM Expression Classes

Each ELM expression has a corresponding class defined in the JavaScript CQL execution framework. These classes all extend a common `Expression` class and define, at a minimum, these components:
Each ELM expression has a corresponding class defined in the TypeScript CQL execution framework. These classes all extend a common `Expression` class and define, at a minimum, these components:

1. A `constructor` that takes a JSON ELM object as its argument
2. An `exec` function that takes a `Context` object as its argument
Expand All @@ -55,16 +56,19 @@ The `constructor` is responsible for setting class properties from the JSON ELM

The following is an example of the `Add` class that corresponds to the JSON ELM example in the previous section:

```js
```typescript
class Add extends Expression {
constructor(json) {
arg1: IntegerLiteral;
arg1: IntegerLiteral;

constructor(json: any) {
super(json);
this.arg1 = new IntegerLiteral(json.operand[0])
this.arg2 = new IntegerLiteral(json.operand[1])
this.arg1 = new IntegerLiteral(json.operand[0]);
this.arg2 = new IntegerLiteral(json.operand[1]);
}

exec(ctx) {
return this.arg1.exec(ctx) + this.arg2.exec(ctx)
exec(ctx: Context) {
return this.arg1.exec(ctx) + this.arg2.exec(ctx);
}
}
```
Expand Down Expand Up @@ -92,8 +96,8 @@ In order for the CQL execution framework to determine if a code is in a valueset
### MessageListener

The CQL specification defines a [Message](https://cql.hl7.org/09-b-cqlreference.html#message) operator that "provides a run-time mechanism for returning messages, warnings, traces, and errors to the calling environment." To support this, the CQL execution framework supports a "MessageListener" API. A MessageListener class must contain an `onMessage` function which will be called by the CQL execution framework if the `condition` passed to the `Message` operator is `true`:
```js
onMessage(source, code, severity, message) {
```typescript
onMessage(source: any, code: string, severity: string, message: string) {
// do something with the message
}
```
Expand Down Expand Up @@ -127,7 +131,7 @@ const parameters = {
new cql.DateTime(2014, 1, 1, 0, 0, 0, 0),
true,
false
);
)
};

const executor = new cql.Executor(lib, codeService, parameters, messageListener);
Expand Down
100 changes: 78 additions & 22 deletions README.md
@@ -1,9 +1,9 @@
[![Build Status](https://travis-ci.org/cqframework/cql-execution.svg?branch=master)](https://travis-ci.org/cqframework/cql-execution)
[![CI Checks](https://github.com/cqframework/cql-execution/actions/workflows/ci-workflow.yml/badge.svg)](https://github.com/cqframework/cql-execution/actions/workflows/ci-workflow.yml)
[![codecov](https://codecov.io/gh/cqframework/cql-execution/branch/master/graph/badge.svg)](https://codecov.io/gh/cqframework/cql-execution)

# CQL Execution Framework

The CQL Execution Framework provides a JavaScript library for executing CQL artifacts expressed as
The CQL Execution Framework provides a TypeScript/JavaScript library for executing CQL artifacts expressed as
JSON ELM.

For more information, see the [CQL Execution Framework Overview](OVERVIEW.md).
Expand Down Expand Up @@ -51,6 +51,9 @@ Implementors should be aware of the following limitations and gaps in `cql-execu
* Unfiltered context retrieves
* Unfiltered context references to other libraries
* External functions
* While the source code of `cql-execution` is in TypeScript, full-fledged typing of the library is not yet implemented
* Conversion from JavaScript to TypeScript was done in [this pull request](https://github.com/cqframework/cql-execution/pull/260),
with the intent on making incremental type improvements in subsequent pull requests.

The above is a partial list covering the most significant limitations. For more details, see the
[CQL_Execution_Features.xlsx](CQL_Execution_Features.xlsx) spreadsheet.
Expand All @@ -59,9 +62,8 @@ The above is a partial list covering the most significant limitations. For more

To use this project, you should perform the following steps:

1. Install [Node.js](http://nodejs.org/)
2. Install [Yarn](https://yarnpkg.com)
mgramigna marked this conversation as resolved.
Show resolved Hide resolved
3. Execute the following from the root directory: `yarn install`
1. Install [Node.js](http://nodejs.org/) (Note: `npm` version `6.x.x` recommended)
2. Execute the following from the root directory: `npm install`

# To Execute Your CQL

Expand Down Expand Up @@ -103,14 +105,70 @@ define InDemographic:
AgeInYearsAt(start of MeasurementPeriod) >= 2 and AgeInYearsAt(start of MeasurementPeriod) < 18
```

## TypeScript Example

Next, we can create a TypeScript file to execute the above CQL. This file will need to contain (or
`import`) JSON patient representations for testing as well. Our example CQL uses a "Simple"
data model developed only for demonstration and testing purposes. In this model, each patient is
represented using a simple JSON object. For ease of use, let's put the file in the `customCQL`
directory:

``` typescript
import cql from '../../src/cql';
import * as measure from './age.json'; // Requires the "resolveJsonModule" compiler option to be "true"

const lib = new cql.Library(measure);
const executor = new cql.Executor(lib);
const psource = new cql.PatientSource([
{
id: '1',
recordType: 'Patient',
name: 'John Smith',
gender: 'M',
birthDate: '1980-02-17T06:15'
},
{
id: '2',
recordType: 'Patient',
name: 'Sally Smith',
gender: 'F',
birthDate: '2007-08-02T11:47'
}
]);

const result = executor.exec(psource);
console.log(JSON.stringify(result, undefined, 2));
```

In the above file, we've assumed the JSON ELM JSON file for the measure is called
`age.json` and is in the same directory as the file that requires is. We've
also assumed a couple of very simple patients. Let's call the file we just created
`exec-age.ts`.

Now we can execute the measure using [ts-node](https://www.npmjs.com/package/ts-node):

``` bash
npx ts-node -O '{ "resolveJsonModule": true }' --files ${path_to_cql-execution}/customCQL/exec-age.ts
```

If all is well, it should print the result object to standard out.

## JavaScript Example

For usage in regular JavaScript, we can refer to the compiled JavaScript in the `lib` directory.
Ensure that this JavaScript is present by running `npm run build` before continuing on to the example.
We will follow the same steps as the above TypeScript example, but our JavaScript code must use `require`
instead of `import`, and will load the `cql-execution` library from the `lib` directory. As before,
let's put the file in the `customCQL` directory:

Next, create a JavaScript file to execute the CQL above. This file will need to contain (or
`require`) JSON patient representations for testing as well. Our example CQL uses a "Simple"
data model developed only for demonstration and testing purposes. In this model, each patient is
represented using a simple JSON object. For ease of use, let's put the file in the `customCQL`
directory:

```js
const cql = require('../src/cql');
const cql = require('../lib/cql');
const measure = require('./age.json');

const lib = new cql.Library(measure);
Expand All @@ -134,9 +192,7 @@ console.log(JSON.stringify(result, undefined, 2));

```

In the above file, we've assumed the JSON ELM JSON file for the measure is called
`age.json` and is in the same directory as the file that requires is. We've
also assumed a couple of very simple patients. Let's call the file we just created
The above file has the same assumptions as the TypeScript example above. Let's call the file we just created
`exec-age.js`.

Now we can execute the measure using Node.js:
Expand All @@ -145,11 +201,11 @@ Now we can execute the measure using Node.js:
node ${path_to_cql-execution}/customCQL/exec-age.js
```

If all is well, it should print the result object to standard out.
If all is well, it should print the result object to standard out, and the output should be identical to that of the TypeScript example.

# To Run the CQL Execution Unit Tests

Execute `yarn test`.
Execute `npm test`.

# To Develop Tests

Expand All @@ -167,7 +223,7 @@ statements that follows the `# And` represents the CQL Library that will be supp
to the "And" test suite.

To convert the CQL to JavaScript containing the JSON ELM representation, execute
`yarn build:test-data`. This will use the java _cql-to-elm_ project to generate the
`npm run build:test-data`. This will use the java _cql-to-elm_ project to generate the
_test/elm/*/data.js_ file containing the following exported variable declaration
(NOTE: It's been slimmed down a bit here to make it easier to read, but nothing substantial
has been removed):
Expand Down Expand Up @@ -244,23 +300,23 @@ module.exports['And'] = {

Notice that since the CQL didn't declare a library name/version, a data model, or a context,
default values were inserted into the CQL at generation time. Now this CQL can be used in a test
defined in _test/elm/*/logical-test.js_. For example:
defined in _test/elm/*/logical-test.ts_. For example:

```js
describe('And', () => {
this.beforeEach(() => {
this.beforeEach(function () {
setup(this, data);
});

it('should execute allTrue as true', () => {
it('should execute allTrue as true', function () {
this.allTrue.exec(this.ctx).should.be.true();
});

it('should execute someTrue as false', () => {
it('should execute someTrue as false', function () {
this.someTrue.exec(this.ctx).should.be.false();
});

it('should execute allFalse as false', () => {
it('should execute allFalse as false', function () {
this.allFalse.exec(this.ctx).should.be.false();
});
});
Expand All @@ -275,16 +331,16 @@ use lowercase first letters even though the CQL expression name starts with an u

# Watching For Changes

Rather than continually having to run `yarn build:test-data` and `yarn:test` after every
Rather than continually having to run `npm run build:test-data` and `npm test` after every
modification to the test data text file, you can setup a process to _watch_ for changes and
regenerate the `data.js` files every time it detects changes in the source text file. Simply
execute `yarn watch:test-data`.
execute `npm run watch:test-data`.

# Pull Requests

If JavaScript source code is modified, `cql4browsers.js` needs to be included in the pull request,
otherwise Travis CI will fail. To generate this file, run:
If TypeScript source code is modified, `cql4browsers.js` needs to be included in the pull request,
otherwise GitHub Actions CI will fail. To generate this file, run:

```
yarn build:all
npm run build:browserify
```
10 changes: 0 additions & 10 deletions babel.config.json

This file was deleted.

6 changes: 3 additions & 3 deletions bin/validate_cql4browsers.sh
@@ -1,7 +1,7 @@
#!/bin/bash

mkdir -p tmp/dist/
git status --porcelain | grep cql4browsers
git status --porcelain | grep cql4browsers.js
if [ $? == 0 ]; then
echo "cql4browsers.js has uncommitted changes. Reset or commit them before continuing."
exit 1
Expand All @@ -13,7 +13,7 @@ if [ $? != 0 ]; then
echo "cql4browsers.js not found. Run this script from the base repository directory."
exit 1
fi
yarn install
npm install

# comm -3 only returns lines that differ between the two files. If none are different, diff will be empty
diff=`diff ./examples/browser/cql4browsers.js.original ./examples/browser/cql4browsers.js`
Expand All @@ -22,7 +22,7 @@ mv ./examples/browser/cql4browsers.js.original ./examples/browser/cql4browsers.j

# Exit with a non-zero code if the diff isn't empty
if [ "$diff" != "" ]; then
echo "cql4browsers.js is out of date. Please run 'yarn install' locally and commit/push the result"
echo "cql4browsers.js is out of date. Please run 'npm install' locally and commit/push the result"
exit 1
fi

Expand Down
9 changes: 4 additions & 5 deletions examples/browser/README.md
Expand Up @@ -5,8 +5,7 @@ does not support other data models or execution on patients.
The browserified code is checked into source control, but if you need to update it,
you can follow these steps:

1. Install [Node.js](http://nodejs.org/)
2. Install [Yarn](https://yarnpkg.com)
3. Execute the following from the _cql-execution_ directory:
1. `yarn install`
2. `cake build:all`
1. Install [Node.js](http://nodejs.org/) (Note: `npm` version `6.x.x` recommended)
2. Execute the following from the _cql-execution_ directory:
1. `npm install`
mgramigna marked this conversation as resolved.
Show resolved Hide resolved
2. `npm run build:all`