Skip to content

Commit 2cd2123

Browse files
authored
feat/migrate to esm only with shared tsconfig (#21)
* chore: add vitest.config.ts with snapshotFormat Prevent truncation of large snapshots by setting `snapshotFormat.maxOutputLength` to `Number.MAX_SAFE_INTEGER`. * chore: migrate typedoc workflow to shared reusable workflow Replace the hand-written TypeDoc deploy steps with the shared `zakodium/workflows/.github/workflows/typedoc.yml@typedoc-v1` reusable workflow. * chore: ignore .claude in .gitignore * test: flatten tests to top-level test() calls Remove the outer `describe` wrappers that only mirrored the filename and switch from `it` to `test` so each assertion sits at the top level of its file. * feat!: migrate package to ESM TypeScript - Set `"type": "module"` and replace `"main"` / `"module"` / `"types"` with a single `"exports"` entry - Drop the CJS dual-build; single `lib/` output via tsconfig.build.json - Extend `@zakodium/tsconfig` and pin TypeScript to 5.x - Local imports use `.ts` extensions; types use `import type` - Add `override` modifier in test subclass - Drop `lib-esm` from .gitignore and from eslint ignores BREAKING CHANGE: package is now ESM-only; consumers using require() must migrate to import, or run Node.js >= 20.19, >= 22.12, or any 24.x or later. * chore: address feedback * chore: fix typescript
1 parent 37d4326 commit 2cd2123

16 files changed

Lines changed: 191 additions & 208 deletions

.github/workflows/typedoc.yml

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
1-
name: Deploy TypeDoc on GitHub pages
1+
name: TypeDoc
22

33
on:
44
workflow_dispatch:
55
release:
66
types: [published]
77

8-
env:
9-
NODE_VERSION: 20.x
10-
ENTRY_FILE: 'src/index.ts'
11-
128
jobs:
13-
deploy:
14-
runs-on: ubuntu-latest
15-
steps:
16-
- uses: actions/checkout@v3
17-
- uses: actions/setup-node@v3
18-
with:
19-
node-version: ${{ env.NODE_VERSION }}
20-
- name: Install dependencies
21-
run: npm install
22-
- name: Build documentation
23-
uses: zakodium/typedoc-action@v2
24-
with:
25-
entry: ${{ env.ENTRY_FILE }}
26-
- name: Deploy to GitHub pages
27-
uses: JamesIves/github-pages-deploy-action@releases/v4
28-
with:
29-
token: ${{ secrets.BOT_TOKEN }}
30-
branch: gh-pages
31-
folder: docs
32-
clean: true
9+
typedoc:
10+
# Documentation: https://github.com/zakodium/workflows#typedoc
11+
uses: zakodium/workflows/.github/workflows/typedoc.yml@typedoc-v1
12+
with:
13+
entry: 'src/index.ts'
14+
secrets:
15+
github-token: ${{ secrets.BOT_TOKEN }}

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,5 @@ jspm_packages
3737
.node_repl_history
3838

3939
lib
40-
41-
lib-esm
4240
docs
41+
.claude

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { defineConfig, globalIgnores } from 'eslint/config';
22
import ts from 'eslint-config-cheminfo-typescript/base';
33

4-
export default defineConfig(globalIgnores(['coverage', 'lib', 'lib-esm']), ts);
4+
export default defineConfig(globalIgnores(['coverage', 'lib']), ts);

package.json

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,27 @@
22
"name": "ml-regression-base",
33
"version": "4.0.1",
44
"description": "Base class for regression modules",
5-
"main": "./lib/index.js",
6-
"module": "./lib-esm/index.js",
7-
"types": "./lib/index.d.ts",
5+
"type": "module",
6+
"exports": {
7+
".": "./lib/index.js"
8+
},
89
"sideEffects": false,
910
"files": [
10-
"src",
1111
"lib",
12-
"lib-esm"
12+
"src"
1313
],
1414
"scripts": {
1515
"check-types": "tsc --noEmit",
16-
"clean": "rimraf lib lib-esm",
16+
"clean": "rimraf lib",
1717
"eslint": "eslint src",
1818
"eslint-fix": "npm run eslint -- --fix",
1919
"prepack": "npm run tsc",
2020
"prettier": "prettier --check src",
2121
"prettier-write": "prettier --write src",
22-
"test": "npm run test-only && npm run eslint && npm run prettier && npm run check-types",
22+
"test": "npm run test-only && npm run check-types && npm run eslint && npm run prettier",
2323
"test-only": "vitest run --coverage",
24-
"tsc": "npm run clean && npm run tsc-cjs && npm run tsc-esm",
25-
"tsc-cjs": "tsc --project tsconfig.cjs.json",
26-
"tsc-esm": "tsc --project tsconfig.esm.json"
24+
"tsc": "npm run clean && npm run tsc-build",
25+
"tsc-build": "tsc --project tsconfig.build.json"
2726
},
2827
"repository": {
2928
"type": "git",
@@ -41,7 +40,9 @@
4140
"is-any-array": "^3.0.0"
4241
},
4342
"devDependencies": {
43+
"@types/node": "^25.6.0",
4444
"@vitest/coverage-v8": "^4.1.4",
45+
"@zakodium/tsconfig": "^1.0.5",
4546
"eslint": "^9.39.4",
4647
"eslint-config-cheminfo-typescript": "^22.0.0",
4748
"prettier": "^3.8.3",

src/BaseRegression.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import type { NumberArray } from 'cheminfo-types';
22
import { isAnyArray } from 'is-any-array';
33

4-
import { checkArrayLength } from './checkArrayLength';
4+
import { checkArrayLength } from './checkArrayLength.ts';
55

66
export interface RegressionScore {
77
r: number;
88
r2: number;
99
chi2: number;
1010
rmsd: number;
1111
}
12+
1213
export class BaseRegression {
1314
constructor() {
1415
if (new.target === BaseRegression) {
@@ -55,15 +56,15 @@ export class BaseRegression {
5556
* Return the correlation coefficient of determination (r) and chi-square.
5657
* @param x - explanatory variable
5758
* @param y - response variable
58-
* @return - Object with further statistics.
59+
* @returns Object with further statistics.
5960
*/
6061
score(x: NumberArray, y: NumberArray): RegressionScore {
6162
checkArrayLength(x, y);
6263

6364
const n = x.length;
6465
const y2: number[] = new Array(n);
6566
for (let i = 0; i < n; i++) {
66-
y2[i] = this._predict(x[i]);
67+
y2[i] = this._predict(x[i] as number);
6768
}
6869

6970
let xSum = 0;
@@ -74,15 +75,17 @@ export class BaseRegression {
7475
let ySquared = 0;
7576
let xY = 0;
7677
for (let i = 0; i < n; i++) {
77-
xSum += y2[i];
78-
ySum += y[i];
79-
xSquared += y2[i] * y2[i];
80-
ySquared += y[i] * y[i];
81-
xY += y2[i] * y[i];
82-
if (y[i] !== 0) {
83-
chi2 += ((y[i] - y2[i]) * (y[i] - y2[i])) / y[i];
78+
const yi = y[i] as number;
79+
const y2i = y2[i] as number;
80+
xSum += y2i;
81+
ySum += yi;
82+
xSquared += y2i * y2i;
83+
ySquared += yi * yi;
84+
xY += y2i * yi;
85+
if (yi !== 0) {
86+
chi2 += ((yi - y2i) * (yi - y2i)) / yi;
8487
}
85-
rmsd += (y[i] - y2[i]) * (y[i] - y2[i]);
88+
rmsd += (yi - y2i) * (yi - y2i);
8689
}
8790

8891
const r =
Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { expect, test } from 'vitest';
22

3-
import { BaseRegression } from '..';
3+
import { BaseRegression } from '../index.ts';
44

55
class NoPredict extends BaseRegression {}
66
class Basic extends BaseRegression {
@@ -9,82 +9,86 @@ class Basic extends BaseRegression {
99
super();
1010
this.factor = factor;
1111
}
12-
_predict(x: number) {
12+
override _predict(x: number) {
1313
return x * this.factor;
1414
}
1515
}
1616

17-
describe('base regression', () => {
18-
it('should not be directly constructable', () => {
19-
expect(() => {
20-
// eslint-disable-next-line no-new
21-
new BaseRegression();
22-
}).toThrow(/BaseRegression must be subclassed/);
23-
});
17+
test('should not be directly constructable', () => {
18+
expect(() => {
19+
// eslint-disable-next-line no-new
20+
new BaseRegression();
21+
}).toThrow(/BaseRegression must be subclassed/);
22+
});
2423

25-
it('should throw if _predict is not implemented', () => {
26-
const reg = new NoPredict();
27-
expect(() => {
28-
reg.predict(0);
29-
}).toThrow(/_predict must be implemented/);
30-
});
24+
test('should throw if _predict is not implemented', () => {
25+
const reg = new NoPredict();
3126

32-
it('should do a basic prediction', () => {
33-
const basic = new Basic(2);
34-
expect(basic.predict(1)).toBe(2);
35-
expect(basic.predict(2)).toBe(4);
36-
expect(basic.predict([2, 3])).toStrictEqual([4, 6]);
37-
});
27+
expect(() => {
28+
reg.predict(0);
29+
}).toThrow(/_predict must be implemented/);
30+
});
31+
32+
test('should do a basic prediction', () => {
33+
const basic = new Basic(2);
34+
35+
expect(basic.predict(1)).toBe(2);
36+
expect(basic.predict(2)).toBe(4);
37+
expect(basic.predict([2, 3])).toStrictEqual([4, 6]);
38+
});
39+
40+
test('should throw on invalid value', () => {
41+
const basic = new Basic(2);
3842

39-
it('should throw on invalid value', () => {
40-
const basic = new Basic(2);
41-
expect(() => {
42-
// @ts-expect-error testing invalid input
43-
basic.predict();
44-
}).toThrow(/must be a number or array/);
43+
expect(() => {
44+
// @ts-expect-error testing invalid input
45+
basic.predict();
46+
}).toThrow(/must be a number or array/);
47+
});
48+
49+
test('should implement dummy predictor functions', () => {
50+
const basic = new Basic(2);
51+
basic.train(); // should not throw
52+
53+
expect(basic.toString()).toBe('');
54+
expect(basic.toLaTeX()).toBe('');
55+
});
56+
57+
test('should implement a scoring function', () => {
58+
const basic = new Basic(2);
59+
60+
expect(basic.score([1, 2], [2, 4])).toStrictEqual({
61+
r: 1,
62+
r2: 1,
63+
chi2: 0,
64+
rmsd: 0,
4565
});
66+
expect(basic.score([1, 2], [2, 4.1]).rmsd).toBe(0.0707106781186545);
4667

47-
it('should implement dummy predictor functions', () => {
48-
const basic = new Basic(2);
49-
basic.train(); // should not throw
50-
expect(basic.toString()).toBe('');
51-
expect(basic.toLaTeX()).toBe('');
68+
expect(basic.score([1, 2], [0.5, 2])).toStrictEqual({
69+
r: 1,
70+
r2: 1,
71+
chi2: 6.5,
72+
rmsd: 1.7677669529663689,
5273
});
5374

54-
it('should implement a scoring function', () => {
55-
const basic = new Basic(2);
56-
expect(basic.score([1, 2], [2, 4])).toStrictEqual({
57-
r: 1,
58-
r2: 1,
59-
chi2: 0,
60-
rmsd: 0,
61-
});
62-
expect(basic.score([1, 2], [2, 4.1]).rmsd).toBe(0.0707106781186545);
63-
64-
expect(basic.score([1, 2], [0.5, 2])).toStrictEqual({
65-
r: 1,
66-
r2: 1,
67-
chi2: 6.5,
68-
rmsd: 1.7677669529663689,
69-
});
70-
71-
expect(basic.score([1, 2], [0, 1])).toStrictEqual({
72-
r: 1,
73-
r2: 1,
74-
chi2: 9,
75-
rmsd: 2.5495097567963922,
76-
});
75+
expect(basic.score([1, 2], [0, 1])).toStrictEqual({
76+
r: 1,
77+
r2: 1,
78+
chi2: 9,
79+
rmsd: 2.5495097567963922,
7780
});
81+
});
7882

79-
it('should throw error if inputs are not arrays or has different length', () => {
80-
const basic = new Basic(2);
81-
expect(() => {
82-
// @ts-expect-error testing invalid input
83-
basic.score(1, 2);
84-
}).toThrow('x and y must be arrays');
83+
test('should throw error if inputs are not arrays or has different length', () => {
84+
const basic = new Basic(2);
8585

86-
expect(() => {
87-
basic.score([1, 2], [2]);
88-
}).toThrow('x and y arrays must have the same length');
89-
});
86+
expect(() => {
87+
// @ts-expect-error testing invalid input
88+
basic.score(1, 2);
89+
}).toThrow('x and y must be arrays');
90+
91+
expect(() => {
92+
basic.score([1, 2], [2]);
93+
}).toThrow('x and y arrays must have the same length');
9094
});
Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { expect, test } from 'vitest';
22

3-
import { checkArrayLength } from '..';
3+
import { checkArrayLength } from '../index.ts';
44

5-
describe('checkArrayLength', () => {
6-
it('throws on different Length', () => {
7-
const expected = /x and y arrays must have the same length/;
8-
expect(() => checkArrayLength([], [1])).toThrow(expected);
9-
expect(() => checkArrayLength([1], [])).toThrow(expected);
10-
expect(() => checkArrayLength([1], [1, 2])).toThrow(expected);
11-
});
5+
test('throws on different Length', () => {
6+
const expected = /x and y arrays must have the same length/;
127

13-
it('throws if not arrays', () => {
14-
const expected = /x and y must be arrays/;
15-
// @ts-expect-error testing invalid input
16-
expect(() => checkArrayLength(null, [1])).toThrow(expected);
17-
// @ts-expect-error testing invalid input
18-
expect(() => checkArrayLength([], null)).toThrow(expected);
19-
// @ts-expect-error testing invalid input
20-
expect(() => checkArrayLength()).toThrow(expected);
21-
// @ts-expect-error testing invalid input
22-
expect(() => checkArrayLength(42, [])).toThrow(expected);
23-
// @ts-expect-error testing invalid input
24-
expect(() => checkArrayLength([1, 2, 3, 4, 5], 'hello')).toThrow(expected);
25-
});
8+
expect(() => checkArrayLength([], [1])).toThrow(expected);
9+
expect(() => checkArrayLength([1], [])).toThrow(expected);
10+
expect(() => checkArrayLength([1], [1, 2])).toThrow(expected);
11+
});
12+
13+
test('throws if not arrays', () => {
14+
const expected = /x and y must be arrays/;
15+
16+
// @ts-expect-error testing invalid input
17+
expect(() => checkArrayLength(null, [1])).toThrow(expected);
18+
// @ts-expect-error testing invalid input
19+
expect(() => checkArrayLength([], null)).toThrow(expected);
20+
// @ts-expect-error testing invalid input
21+
expect(() => checkArrayLength()).toThrow(expected);
22+
// @ts-expect-error testing invalid input
23+
expect(() => checkArrayLength(42, [])).toThrow(expected);
24+
// @ts-expect-error testing invalid input
25+
expect(() => checkArrayLength([1, 2, 3, 4, 5], 'hello')).toThrow(expected);
26+
});
2627

27-
it('correct result', () => {
28-
expect(checkArrayLength([1, 2], [2, 3])).toBeUndefined();
29-
expect(
30-
checkArrayLength(new Float64Array([1, 2]), new Float64Array([2, 3])),
31-
).toBeUndefined();
32-
expect(checkArrayLength([1, 2], new Float64Array([2, 3]))).toBeUndefined();
33-
});
28+
test('correct result', () => {
29+
expect(checkArrayLength([1, 2], [2, 3])).toBeUndefined();
30+
expect(
31+
checkArrayLength(new Float64Array([1, 2]), new Float64Array([2, 3])),
32+
).toBeUndefined();
33+
expect(checkArrayLength([1, 2], new Float64Array([2, 3]))).toBeUndefined();
3434
});

0 commit comments

Comments
 (0)