Skip to content

Commit b0556b4

Browse files
authored
feat!: move to ESM; drop Node 16.x support (#393)
* feat!: use ESM; upgrade typescript * fix!: require node v18.x or higher * chore: migrate jest expect to assert * refactor: move benchmarks to ts * refactor: swap jest with node:test * fixup: run benchmarks fully * fixup: use v20 for github tasks - fixup: add benchmarks using v20
1 parent 6bd63f3 commit b0556b4

31 files changed

+1119
-5513
lines changed

.github/workflows/node.js.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
strategy:
1818
matrix:
19-
node-version: [16.x, 18.x]
19+
node-version: [18.x, 20.x, 22.x]
2020
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2121

2222
steps:

.github/workflows/npm-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
persist-credentials: false
1616
- uses: actions/setup-node@v3
1717
with:
18-
node-version: lts/*
18+
node-version-file: .nvmrc
1919
cache: 'npm'
2020
registry-url: https://registry.npmjs.org/
2121
- run: npm ci

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v18
1+
20
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
import { PolicyDocument } from "../../index.js";
12
import {
23
ContextualAllowStatement,
34
ContextualDenyStatement,
45
GlobAllStatement,
56
GlobEndStatement,
67
GlobStartStatement,
78
MultipleActionsStatement,
8-
} from "./statements.mjs";
9+
} from "./statements.js";
910

10-
export const ComplexPolicy = {
11+
export const ComplexPolicy: PolicyDocument = {
1112
statement: [
1213
GlobAllStatement,
1314
GlobStartStatement,
@@ -18,6 +19,6 @@ export const ComplexPolicy = {
1819
],
1920
};
2021

21-
export const GlobAllPolicy = {
22+
export const GlobAllPolicy: PolicyDocument = {
2223
statement: [GlobAllStatement],
2324
};
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
1-
export const GlobAllStatement = {
1+
import type { PolicyStatement } from "../../index.js";
2+
3+
export const GlobAllStatement: PolicyStatement = {
24
sid: "GlobAllStatement",
35
action: "*",
46
effect: "allow",
57
constraint: true,
68
};
79

8-
export const GlobStartStatement = {
10+
export const GlobStartStatement: PolicyStatement = {
911
sid: "GlobStartStatement",
1012
action: "*:documents",
1113
effect: "allow",
1214
constraint: true,
1315
};
1416

15-
export const GlobEndStatement = {
17+
export const GlobEndStatement: PolicyStatement = {
1618
sid: "GlobEndStatement",
1719
action: "documents:*",
1820
effect: "allow",
1921
constraint: true,
2022
};
2123

22-
export const BasicAllowStatement = {
24+
export const BasicAllowStatement: PolicyStatement = {
2325
sid: "BasicAllowStatement",
2426
action: "create",
2527
effect: "allow",
2628
constraint: true,
2729
};
2830

29-
export const MultipleActionsStatement = {
31+
export const MultipleActionsStatement: PolicyStatement = {
3032
sid: "MultipleActionsStatement",
3133
action: ["create", "read"],
3234
effect: "allow",
3335
constraint: true,
3436
};
3537

36-
export const ContextualAllowStatement = {
38+
export const ContextualAllowStatement: PolicyStatement = {
3739
sid: "ContextualAllowStatement",
3840
action: "create",
3941
effect: "allow",
@@ -42,7 +44,7 @@ export const ContextualAllowStatement = {
4244
},
4345
};
4446

45-
export const ContextualGlobAllowStatement = {
47+
export const ContextualGlobAllowStatement: PolicyStatement = {
4648
sid: "ContextualGlobAllowStatement",
4749
action: "documents:*",
4850
effect: "allow",
@@ -51,7 +53,7 @@ export const ContextualGlobAllowStatement = {
5153
},
5254
};
5355

54-
export const ContextualGlobAllAllowStatement = {
56+
export const ContextualGlobAllAllowStatement: PolicyStatement = {
5557
sid: "ContextualGlobAllAllowStatement",
5658
action: "*",
5759
effect: "allow",
@@ -60,7 +62,7 @@ export const ContextualGlobAllAllowStatement = {
6062
},
6163
};
6264

63-
export const ContextualDenyStatement = {
65+
export const ContextualDenyStatement: PolicyStatement = {
6466
sid: "ContextualDenyStatement",
6567
action: "create",
6668
effect: "deny",
@@ -69,7 +71,7 @@ export const ContextualDenyStatement = {
6971
},
7072
};
7173

72-
export const ContextualGlobDenyStatement = {
74+
export const ContextualGlobDenyStatement: PolicyStatement = {
7375
sid: "ContextualGlobDenyStatement",
7476
action: "documents:*",
7577
effect: "deny",
@@ -78,7 +80,7 @@ export const ContextualGlobDenyStatement = {
7880
},
7981
};
8082

81-
export const ContextualGlobAllDenyStatement = {
83+
export const ContextualGlobAllDenyStatement: PolicyStatement = {
8284
sid: "ContextualGlobAllDenyStatement",
8385
action: "*",
8486
effect: "deny",
@@ -87,7 +89,7 @@ export const ContextualGlobAllDenyStatement = {
8789
},
8890
};
8991

90-
const toContext = (subjectId) => ({
92+
const toContext = (subjectId: string) => ({
9193
subject: {
9294
id: subjectId,
9395
},
@@ -98,6 +100,9 @@ const toContext = (subjectId) => ({
98100
globDeniedBy: "globDenied",
99101
},
100102
});
103+
104+
export type BencharkContext = ReturnType<typeof toContext>;
105+
101106
export const allowContext = toContext("allowed");
102107
export const denyContext = toContext("denied");
103108
export const allowAllContext = toContext("globAllowed");
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import Benchmark from "benchmark";
2-
import { parsePolicyStatement } from "../dist/lib/parsed-policy-statement.js";
2+
import { parsePolicyStatement } from "../index.js";
33
import {
44
BasicAllowStatement,
55
GlobAllStatement,
66
GlobStartStatement,
77
MultipleActionsStatement,
8-
} from "./__fixtures__/statements.mjs";
8+
} from "./__fixtures__/statements.js";
99

1010
export function buildParserBenchmarks() {
1111
return new Benchmark.Suite("parsePolicyStatement Benchmarks")
Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import Benchmark from "benchmark";
2+
import assert from "node:assert";
23
import {
34
CachedStatementsStore,
45
IndexedStatementsStore,
5-
} from "../dist/lib/store/index.js";
6-
import { PolicyResolver } from "../dist/lib/policy-resolver.js";
6+
PolicyResolver,
7+
PolicyStatementStore,
8+
parsePolicyStatement,
9+
} from "../index.js";
710
import {
811
allowAllContext,
912
allowContext,
1013
BasicAllowStatement,
14+
BencharkContext,
1115
ContextualAllowStatement,
1216
ContextualDenyStatement,
1317
ContextualGlobAllAllowStatement,
@@ -19,9 +23,7 @@ import {
1923
GlobEndStatement,
2024
GlobStartStatement,
2125
MultipleActionsStatement,
22-
} from "./__fixtures__/statements.mjs";
23-
import { parsePolicyStatement } from "../dist/lib/parsed-policy-statement.js";
24-
import assert from "assert";
26+
} from "./__fixtures__/statements.js";
2527

2628
const indexed = new IndexedStatementsStore();
2729
indexed.addAll(
@@ -60,19 +62,34 @@ const resCachedNoCtx = new PolicyResolver(cachedNoCtx);
6062
*
6163
* @param {IndexedStatementsStore|CachedStatementsStore} store
6264
*/
63-
const withNewResolver = (store, expected, action, ctx) => {
65+
const withNewResolver = (
66+
store: PolicyStatementStore,
67+
expected: boolean,
68+
action: string,
69+
ctx?: BencharkContext,
70+
) => {
6471
const res = new PolicyResolver(store);
72+
// @ts-expect-error removeAllListeners is not present on emitter
6573
store.removeAllListeners();
6674
assert(res.can(action, ctx) === expected);
6775
};
6876

69-
const withResolver = (resolver, expected, action, ctx) => {
77+
const withResolver = (
78+
resolver: PolicyResolver,
79+
expected: boolean,
80+
action: string,
81+
ctx?: BencharkContext,
82+
) => {
7083
assert(resolver.can(action, ctx) === expected);
7184
};
7285

7386
// labels are `{label}:{resolverType}:{storeType}:{effect}`
7487

75-
const addActionTests = (suite, action, label) => {
88+
const addActionTests = (
89+
suite: Benchmark.Suite,
90+
action: string,
91+
label: string,
92+
) => {
7693
suite
7794
.add(`${label}:new:uncached:ctx:allow`, () =>
7895
withNewResolver(indexed, true, action, allowContext),

benchmarks/results.md

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,58 @@
22

33
Performed on: Tue May 21 2024
44

5-
Node version: v18.20.3
5+
Node version: v20.12.2
66
Platform/Architecture: darwin/arm64
77

88
## PolicyResolver Benchmarks
99

1010
| Test Name | Pass/Fail | ops/sec | variance | samples (n) |
1111
| --------- | --------- | ------- | -------- | ----------- |
12-
| globAll:new:uncached:ctx:allow | PASS | 221,995.261 ops/sec | ±1.44% | 88 samples |
13-
| globAll:new:cached:ctx:allow | PASS | 227,358.726 ops/sec | ±1.47% | 89 samples |
14-
| globAll:instance:uncached:ctx:deny | PASS | 1,775,213.305 ops/sec | ±0.17% | 101 samples |
15-
| globAll:instance:cached:ctx:deny | PASS | 1,785,473.205 ops/sec | ±0.10% | 101 samples |
16-
| globStart:new:uncached:ctx:allow | PASS | 184,047.8 ops/sec | ±0.92% | 94 samples |
17-
| globStart:new:cached:ctx:allow | PASS | 188,287.008 ops/sec | ±0.84% | 92 samples |
18-
| globStart:new:uncached:ctx:deny | PASS | 226,179.596 ops/sec | ±0.75% | 91 samples |
19-
| globStart:new:cached:ctx:deny | PASS | 234,748.535 ops/sec | ±0.77% | 92 samples |
20-
| globStart:new:uncached:noctx:allow | PASS | 308,131.673 ops/sec | ±1.46% | 91 samples |
21-
| globStart:new:cached:noctx:allow | PASS | 326,557.043 ops/sec | ±1.00% | 93 samples |
22-
| globStart:instance:uncached:ctx:allow | PASS | 473,234.682 ops/sec | ±0.13% | 94 samples |
23-
| globStart:instance:cached:ctx:allow | PASS | 470,377.16 ops/sec | ±0.28% | 96 samples |
24-
| globStart:instance:uncached:ctx:deny | PASS | 890,055.58 ops/sec | ±0.43% | 95 samples |
25-
| globStart:instance:cached:ctx:deny | PASS | 914,060.697 ops/sec | ±0.16% | 100 samples |
26-
| globStart:instance:uncached:noctx:allow | PASS | 11,305,477.395 ops/sec | ±1.56% | 96 samples |
27-
| globStart:instance:cached:noctx:allow | PASS | 11,323,835.775 ops/sec | ±1.33% | 98 samples |
28-
| exact:new:uncached:ctx:allow | PASS | 180,006.897 ops/sec | ±1.00% | 93 samples |
29-
| exact:new:cached:ctx:allow | PASS | 185,633.683 ops/sec | ±0.98% | 91 samples |
30-
| exact:new:uncached:ctx:deny | PASS | 221,323.028 ops/sec | ±0.83% | 91 samples |
31-
| exact:new:cached:ctx:deny | PASS | 231,102.935 ops/sec | ±0.77% | 90 samples |
32-
| exact:new:uncached:noctx:allow | PASS | 311,868.661 ops/sec | ±0.75% | 91 samples |
33-
| exact:new:cached:noctx:allow | PASS | 320,718.127 ops/sec | ±0.82% | 91 samples |
34-
| exact:instance:uncached:ctx:allow | PASS | 475,819.535 ops/sec | ±0.11% | 101 samples |
35-
| exact:instance:cached:ctx:allow | PASS | 477,862.325 ops/sec | ±0.09% | 102 samples |
36-
| exact:instance:uncached:ctx:deny | PASS | 913,911.153 ops/sec | ±0.10% | 100 samples |
37-
| exact:instance:cached:ctx:deny | PASS | 913,518.19 ops/sec | ±0.13% | 100 samples |
38-
| exact:instance:uncached:noctx:allow | PASS | 11,285,403.062 ops/sec | ±1.54% | 96 samples |
39-
| exact:instance:cached:noctx:allow | PASS | 11,143,168.047 ops/sec | ±0.73% | 99 samples |
12+
| globAll:new:uncached:ctx:allow | PASS | 377,957.322 ops/sec | ±0.21% | 95 samples |
13+
| globAll:new:cached:ctx:allow | PASS | 395,191.764 ops/sec | ±0.19% | 96 samples |
14+
| globAll:instance:uncached:ctx:deny | PASS | 2,073,325.264 ops/sec | ±0.18% | 100 samples |
15+
| globAll:instance:cached:ctx:deny | PASS | 2,053,311.602 ops/sec | ±0.15% | 99 samples |
16+
| globStart:new:uncached:ctx:allow | PASS | 272,972.469 ops/sec | ±0.12% | 100 samples |
17+
| globStart:new:cached:ctx:allow | PASS | 285,783.376 ops/sec | ±0.20% | 102 samples |
18+
| globStart:new:uncached:ctx:deny | PASS | 367,638.211 ops/sec | ±0.14% | 100 samples |
19+
| globStart:new:cached:ctx:deny | PASS | 389,738.979 ops/sec | ±0.23% | 96 samples |
20+
| globStart:new:uncached:noctx:allow | PASS | 567,178.046 ops/sec | ±0.65% | 93 samples |
21+
| globStart:new:cached:noctx:allow | PASS | 609,382.161 ops/sec | ±0.51% | 96 samples |
22+
| globStart:instance:uncached:ctx:allow | PASS | 511,687.007 ops/sec | ±0.50% | 100 samples |
23+
| globStart:instance:cached:ctx:allow | PASS | 518,035.77 ops/sec | ±0.23% | 94 samples |
24+
| globStart:instance:uncached:ctx:deny | PASS | 1,033,406.199 ops/sec | ±0.29% | 93 samples |
25+
| globStart:instance:cached:ctx:deny | PASS | 1,035,170.917 ops/sec | ±0.21% | 101 samples |
26+
| globStart:instance:uncached:noctx:allow | PASS | 26,262,649.789 ops/sec | ±0.30% | 98 samples |
27+
| globStart:instance:cached:noctx:allow | PASS | 28,299,641.361 ops/sec | ±0.58% | 99 samples |
28+
| exact:new:uncached:ctx:allow | PASS | 268,255.879 ops/sec | ±0.21% | 98 samples |
29+
| exact:new:cached:ctx:allow | PASS | 280,607.282 ops/sec | ±0.40% | 95 samples |
30+
| exact:new:uncached:ctx:deny | PASS | 360,958.252 ops/sec | ±0.22% | 101 samples |
31+
| exact:new:cached:ctx:deny | PASS | 384,227.956 ops/sec | ±0.18% | 98 samples |
32+
| exact:new:uncached:noctx:allow | PASS | 566,359.893 ops/sec | ±0.43% | 97 samples |
33+
| exact:new:cached:noctx:allow | PASS | 598,700.269 ops/sec | ±0.54% | 95 samples |
34+
| exact:instance:uncached:ctx:allow | PASS | 515,148.794 ops/sec | ±0.36% | 100 samples |
35+
| exact:instance:cached:ctx:allow | PASS | 516,762.903 ops/sec | ±0.35% | 98 samples |
36+
| exact:instance:uncached:ctx:deny | PASS | 1,028,596.301 ops/sec | ±0.21% | 96 samples |
37+
| exact:instance:cached:ctx:deny | PASS | 1,031,449.444 ops/sec | ±0.25% | 99 samples |
38+
| exact:instance:uncached:noctx:allow | PASS | 28,174,988.938 ops/sec | ±0.89% | 95 samples |
39+
| exact:instance:cached:noctx:allow | PASS | 28,417,978.32 ops/sec | ±0.22% | 98 samples |
4040

4141
## PolicyDocumentValidator Benchmarks
4242

4343
| Test Name | Pass/Fail | ops/sec | variance | samples (n) |
4444
| --------- | --------- | ------- | -------- | ----------- |
45-
| new | PASS | 458.683 ops/sec | ±0.85% | 95 samples |
46-
| new:glob | PASS | 471.335 ops/sec | ±0.45% | 93 samples |
47-
| getInstance | PASS | 633,690.686 ops/sec | ±0.15% | 100 samples |
48-
| getInstance:glob | PASS | 7,139,705.793 ops/sec | ±0.14% | 97 samples |
49-
| reuse | PASS | 651,603.1 ops/sec | ±0.11% | 102 samples |
50-
| reuse:glob | PASS | 7,923,267.432 ops/sec | ±0.17% | 95 samples |
45+
| new | PASS | 458.78 ops/sec | ±0.80% | 94 samples |
46+
| new:glob | PASS | 454.211 ops/sec | ±1.17% | 89 samples |
47+
| getInstance | PASS | 696,991.616 ops/sec | ±0.22% | 101 samples |
48+
| getInstance:glob | PASS | 7,973,198.177 ops/sec | ±0.94% | 95 samples |
49+
| reuse | PASS | 707,386.955 ops/sec | ±0.65% | 97 samples |
50+
| reuse:glob | PASS | 8,554,128.721 ops/sec | ±0.59% | 92 samples |
5151

5252
## parsePolicyStatement Benchmarks
5353

5454
| Test Name | Pass/Fail | ops/sec | variance | samples (n) |
5555
| --------- | --------- | ------- | -------- | ----------- |
56-
| single | PASS | 16,385,095.252 ops/sec | ±0.26% | 100 samples |
57-
| multiple | PASS | 13,693,743.186 ops/sec | ±0.35% | 95 samples |
58-
| glob | PASS | 27,931,013.283 ops/sec | ±0.23% | 98 samples |
59-
| regex | PASS | 2,958,610.099 ops/sec | ±0.61% | 96 samples |
56+
| single | PASS | 17,905,358.195 ops/sec | ±0.36% | 96 samples |
57+
| multiple | PASS | 15,119,110.363 ops/sec | ±0.88% | 99 samples |
58+
| glob | PASS | 28,698,364.571 ops/sec | ±1.15% | 100 samples |
59+
| regex | PASS | 3,021,340.42 ops/sec | ±0.25% | 99 samples |
Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import Benchmark from "benchmark";
12
import process, { stderr, stdout } from "node:process";
2-
import { buildParserBenchmarks } from "./parser.benchmark.mjs";
3-
import { buildPolicyDocumentValidatorBenchmarks } from "./validator.benchmark.mjs";
4-
import { buildPolicyResolverBenchmarks } from "./policy-resolver.benchmark.mjs";
3+
import { buildParserBenchmarks } from "./parser.benchmark.js";
4+
import { buildPolicyDocumentValidatorBenchmarks } from "./validator.benchmark.js";
5+
import { buildPolicyResolverBenchmarks } from "./policy-resolver.benchmark.js";
56

6-
/** @param {import('benchmark').Suite} suite */
7-
function runSuite(suite) {
7+
function runSuite(suite: Benchmark.Suite) {
88
stdout.write(`
99
## ${suite.name}
1010
@@ -13,26 +13,28 @@ function runSuite(suite) {
1313
`);
1414

1515
suite
16-
.on("cycle", (event) => {
17-
/** @type {import('benchmark').Target} */
16+
.on("cycle", (event: Benchmark.Event) => {
1817
const bench = event.target;
1918

20-
if (bench.error) {
19+
// @ts-expect-error target error does not seem to exist
20+
if (typeof bench.error !== "undefined") {
2121
stderr.write(
2222
`${JSON.stringify({
2323
suite: suite.name,
2424
bench: bench.name,
25+
// @ts-expect-error target error does to seem to exist
2526
err: String(bench.error),
2627
})}\n`,
2728
);
2829
}
2930

3031
const cells = [
3132
bench.name,
33+
// @ts-expect-error target error does to seem to exist
3234
bench.error ? "FAIL" : "PASS",
33-
`${bench.hz.toLocaleString()} ops/sec`,
34-
`\xb1${bench.stats.rme.toFixed(2)}%`,
35-
`${bench.stats.sample.length} samples`,
35+
`${bench.hz?.toLocaleString()} ops/sec`,
36+
`\xb1${bench.stats?.rme.toFixed(2)}%`,
37+
`${bench.stats?.sample.length} samples`,
3638
].join(" | ");
3739

3840
stdout.write(["|", cells, "|\n"].join(" "));
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Benchmark from "benchmark";
2-
import { PolicyDocumentValidator } from "../dist/lib/validator/policy-validator.js";
3-
import { ComplexPolicy, GlobAllPolicy } from "./__fixtures__/policies.mjs";
2+
import { PolicyDocumentValidator } from "../index.js";
3+
import { ComplexPolicy, GlobAllPolicy } from "./__fixtures__/policies.js";
44

55
const validator = new PolicyDocumentValidator();
66

0 commit comments

Comments
 (0)