@regex-designer/js 0.0.0-stage → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 loganmzz
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 CHANGED
@@ -1,3 +1,243 @@
1
- # Temporary Holding Version
1
+ # Regular Expression Designer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A small TypeScript library for composing JavaScript regular expressions in a readable, reusable way.
4
+
5
+ ## API
6
+
7
+ The library exposes a default factory and a `RegexDesigner` class. Each builder acts like a lazy regex composer: methods return template objects that render their pattern only when stringified (e.g. `toString()`) or invoked to compile to a native `RegExp`.
8
+
9
+ ```ts
10
+ import createDesigner from '@regex-designer/js';
11
+
12
+ const r$ = createDesigner()
13
+ .set('number', r$.qty([1, Infinity], /[0-9]/));
14
+ .set('identifier', r$.qty([1, Infinity], /[a-z0-9-]/));
15
+
16
+ const template = r$.named(
17
+ 'version',
18
+ r$.var('number'), /\./, r$.var('number'), /\./, r$.var('number'),
19
+ r$.opt('-', r$.named('prerelease', r$.var('identifier'))),
20
+ );
21
+ const regex = template('i'); // Compile template by using it as a function
22
+ regex.test('12.3.4-beta-1'); // true
23
+ regex.exec('12.3.4-beta-1')?.groups; // { version: '12.3.4-beta-1', prerelease: 'beta-1' }
24
+ ```
25
+
26
+ ### tpl(...patterns)
27
+
28
+ ```ts
29
+ tpl(...patterns: RegexValue[]): RegexTemplate
30
+ ```
31
+
32
+ Concatenates pattern parts as-is, without adding grouping, alternation, or escaping:
33
+
34
+ ```ts
35
+ r$.tpl('A', r$.raw('B.C')); // AB\\.C
36
+ ```
37
+
38
+ ### default
39
+
40
+ ```ts
41
+ default function(): RegexDesigner
42
+ ```
43
+
44
+ Examples:
45
+
46
+ ```ts
47
+ import createDesigner from '@regex-designer/js';
48
+
49
+ const r$ = createDesigner();
50
+ r$.named('user', /[^@]+/, '@', /[a-z]+/); // (?<user>[^@]+@[a-z]+)
51
+ ```
52
+
53
+ ### or(...patterns)
54
+
55
+ ```ts
56
+ or(...patterns: RegexValue[]): RegexTemplate
57
+ ```
58
+
59
+ Examples:
60
+
61
+ ```ts
62
+ r$.or('A', 'B'); // A|B
63
+ r$.or(/foo/, 'bar', r$.raw('baz')); // foo|bar|baz
64
+ ```
65
+
66
+ ### named(name, ...patterns)
67
+
68
+ ```ts
69
+ named(name: string, ...patterns: RegexValue[]): RegexTemplate
70
+ ```
71
+
72
+ Examples:
73
+
74
+ ```ts
75
+ r$.named('value', 'A', 'B'); // (?<value>AB)
76
+ r$.named('word', r$.or('cat', 'dog')); // (?<word>cat|dog)
77
+ ```
78
+
79
+ ### group(options, ...patterns)
80
+
81
+ ```ts
82
+ group(options: GroupOptions, ...patterns: RegexValue[]): RegexTemplate
83
+ type GroupOptions = boolean | { name: string } | { capture: boolean };
84
+ ```
85
+
86
+ Examples:
87
+
88
+ ```ts
89
+ r$.group(false, 'A', 'B'); // (?:AB)
90
+ r$.group({ name: 'part' }, 'A', 'B'); // (?<part>AB)
91
+ r$.group({ capture: false }, 'A', 'B'); // (?:AB)
92
+ ```
93
+
94
+ Options:
95
+
96
+ - `options` can be a boolean or an object.
97
+ - `true` creates a normal capturing group.
98
+ - `false` creates a non-capturing group.
99
+ - `group({ name: 'x' })` creates a named capturing group.
100
+ - `name` and `capture` are mutually exclusive; passing both throws a `TypeError`.
101
+
102
+ ### opt(...patterns)
103
+
104
+ ```ts
105
+ opt(...patterns: RegexValue[]): RegexTemplate
106
+ ```
107
+
108
+ Examples:
109
+
110
+ ```ts
111
+ r$.opt('C'); // (?:C)?
112
+ r$.opt(r$.named('value', 'A', 'B')); // (?:(?<value>AB))?
113
+ ```
114
+
115
+ ### qty(quantity, ...patterns)
116
+
117
+ ```ts
118
+ qty(quantity: QuantityOptions, ...patterns: RegexValue[]): RegexTemplate
119
+ type QuantityOptions = number | [min: number, max?: number] | QuantityNamedOptions;
120
+ type QuantityNamedOptions = ({ exact: number } | { min: number; max?: number; }) & { greedy?: boolean };
121
+ ```
122
+
123
+ Examples:
124
+
125
+ ```ts
126
+ r$.qty(3, 'A', 'B'); // (?:AB){3}
127
+ r$.qty([1, 3], 'A', 'B'); // (?:AB){1,3}
128
+ r$.qty({ min: 2, max: 5, greedy: false }, 'A', 'B'); // (?:AB){2,5}?
129
+ r$.qty({ exact: 2 }, 'A', 'B'); // (?:AB){2}
130
+ ```
131
+
132
+ Options:
133
+
134
+ - `number` means an exact repetition count.
135
+ - `[min, max?]` means a range, with `max` omitted for open-ended repetition.
136
+ - `{ exact }` is a shorthand for exact repetition.
137
+ - `{ min, max? }` creates a bounded repetition.
138
+ - `max` can be `Infinity` for open-ended repetition.
139
+ - `{ greedy?: boolean }` for lazy quantifiers.
140
+ - Quantifiers are greedy by default.
141
+
142
+ ### raw(...strings)
143
+
144
+ ```ts
145
+ raw(...patterns: string[]): RegexTemplate
146
+ ```
147
+
148
+ Examples:
149
+
150
+ ```ts
151
+ r$.raw('a.b', '*').source; // a\.b\*
152
+ r$.raw('https://example.com') // https://example\.com
153
+ ```
154
+
155
+ ### ref(reference)
156
+
157
+ ```ts
158
+ ref(reference: number | string): RegexTemplate
159
+ ```
160
+
161
+ Examples:
162
+
163
+ ```ts
164
+ // (B)\1
165
+ r$.tpl(
166
+ r$.group(true, 'B'),
167
+ r$.ref(1),
168
+ );
169
+
170
+ // (?<name>B)\k<name>
171
+ r$.tpl(
172
+ r$.named('name', 'B'),
173
+ r$.ref('name'),
174
+ );
175
+ ```
176
+
177
+ ### ahead(...patterns) / ahead_pos(...patterns) / ahead_neg(...patterns)
178
+
179
+ ```ts
180
+ ahead(positive: boolean, ...patterns: RegexValue[]): RegexTemplate
181
+ ahead_pos(...patterns: RegexValue[]): RegexTemplate
182
+ ahead_neg(...patterns: RegexValue[]): RegexTemplate
183
+ ```
184
+
185
+ Examples:
186
+
187
+ ```ts
188
+ r$.ahead_pos('foo'); // (?=foo)
189
+ r$.ahead_neg('bar'); // (?!bar)
190
+ ```
191
+
192
+ ### behind(...patterns) / behind_pos(...patterns) / behind_neg(...patterns)
193
+
194
+ ```ts
195
+ behind(positive: boolean, ...patterns: RegexValue[]): RegexTemplate
196
+ behind_pos(...patterns: RegexValue[]): RegexTemplate
197
+ behind_neg(...patterns: RegexValue[]): RegexTemplate
198
+ ```
199
+
200
+ Examples:
201
+
202
+ ```ts
203
+ r$.behind_pos('foo'); // (?<=foo)
204
+ r$.behind_neg('bar'); // (?<!bar)
205
+ ```
206
+
207
+ ### set(name, pattern) / var(name)
208
+
209
+ ```ts
210
+ set(name: string, patterns: ...RegexValue[]): this
211
+ var(name: string): RegexTemplate
212
+ ```
213
+
214
+ Examples:
215
+
216
+ ```ts
217
+ r$.set('word', r$.or('cat', 'dog'));
218
+ r$.var('word'); // cat|dog
219
+ ```
220
+
221
+ Options:
222
+
223
+ - `set()` stores a reusable pattern..
224
+ - `var()` resolves that pattern lazily when rendering.
225
+ - Variables are scoped to the builder instance, so separate builders can reuse the same names without conflict.
226
+ - Rendering an unset variable throws an error.
227
+ - Circular dependencies between variables also throw an error.
228
+
229
+ ## Development
230
+
231
+ See [AGENTS.md](AGENTS.md) for design choices and contribution conventions shared
232
+ by human contributors and AI coding assistants.
233
+
234
+ The project relies on Node.js 24.x:
235
+
236
+ ```sh
237
+ npm ci
238
+ npm run check
239
+ ```
240
+
241
+ The build emits ESM JavaScript, TypeScript declarations, and source maps in
242
+ `dist/`. Tests are written in TypeScript, type-checked, and executed with the
243
+ native Node.js test runner against the built library.
@@ -0,0 +1,83 @@
1
+ /** A lazy regex template that can render pattern text or compile to a native RegExp. */
2
+ export interface RegexTemplate {
3
+ /** Compile the rendered pattern with the supplied flags. */
4
+ (flags?: string): RegExp;
5
+ /** Render the pattern; when compiling, validates embedded Unicode flags. */
6
+ render(flags?: string, compiling?: boolean): string;
7
+ /** Render the pattern text without compiling it. */
8
+ toString(): string;
9
+ }
10
+ /** Check whether a value is a regex template. */
11
+ export declare function isRegexTemplate(value: unknown): value is RegexTemplate;
12
+ /**
13
+ * A regex source string, native RegExp, or lazy regex template.
14
+ * Strings are regex syntax, not escaped literals. Embedded RegExp values scope
15
+ * their i, m, and s flags locally, require u or v when compiling, and do not
16
+ * carry over d, g, or y.
17
+ */
18
+ export type RegexValue = string | RegExp | RegexTemplate;
19
+ /** Mutually exclusive settings for a named capturing group or an unnamed group. */
20
+ export type GroupNamedOptions = {
21
+ name: string;
22
+ capture?: never;
23
+ } | {
24
+ name?: never;
25
+ capture?: boolean;
26
+ };
27
+ /** Configure a group with a capture boolean or named-group options. */
28
+ export type GroupOptions = GroupNamedOptions | boolean;
29
+ /** Repetition bounds; specify either an exact count or a minimum and optional maximum. */
30
+ export type QuantityNamedOptions = ({
31
+ exact: number;
32
+ min?: never;
33
+ max?: never;
34
+ } | {
35
+ exact?: never;
36
+ min: number;
37
+ max?: number;
38
+ }) & {
39
+ greedy?: boolean;
40
+ };
41
+ /** Repetition count, [minimum, maximum] tuple, or named bounds. */
42
+ export type QuantityOptions = number | [min: number, max?: number] | QuantityNamedOptions;
43
+ /** Compose lazy regex templates and resolve reusable variables in an isolated scope. */
44
+ export declare class RegexDesigner {
45
+ private readonly variables;
46
+ private readonly resolvingVariables;
47
+ /** Concatenate patterns without adding groups or escaping. */
48
+ tpl(...patterns: RegexValue[]): RegexTemplate;
49
+ /** Join patterns with alternation without adding groups. */
50
+ or(...patterns: RegexValue[]): RegexTemplate;
51
+ /** Create a named capturing group around the concatenated patterns. */
52
+ named(name: string, ...patterns: RegexValue[]): RegexTemplate;
53
+ /** Group patterns, optionally capturing them or assigning a capture name. */
54
+ group(options: GroupOptions, ...patterns: RegexValue[]): RegexTemplate;
55
+ /** Make the concatenated patterns optional using a non-capturing group. */
56
+ opt(...patterns: RegexValue[]): RegexTemplate;
57
+ /** Repeat the concatenated patterns according to the supplied bounds. */
58
+ qty(quantity: QuantityOptions, ...patterns: RegexValue[]): RegexTemplate;
59
+ /** Escape literal text for use in a regex pattern. */
60
+ raw(...patterns: string[]): RegexTemplate;
61
+ /** Create a positive or negative lookahead assertion. */
62
+ ahead(positive: boolean, ...patterns: RegexValue[]): RegexTemplate;
63
+ /** Create a positive lookahead assertion. */
64
+ ahead_pos(...patterns: RegexValue[]): RegexTemplate;
65
+ /** Create a negative lookahead assertion. */
66
+ ahead_neg(...patterns: RegexValue[]): RegexTemplate;
67
+ /** Create a positive or negative lookbehind assertion. */
68
+ behind(positive: boolean, ...patterns: RegexValue[]): RegexTemplate;
69
+ /** Create a positive lookbehind assertion. */
70
+ behind_pos(...patterns: RegexValue[]): RegexTemplate;
71
+ /** Create a negative lookbehind assertion. */
72
+ behind_neg(...patterns: RegexValue[]): RegexTemplate;
73
+ /** Create a numeric or named backreference. */
74
+ ref(reference: number | string): RegexTemplate;
75
+ /** Define or replace a lazy pattern variable on this designer. */
76
+ set(name: string, ...patterns: RegexValue[]): this;
77
+ private resolveVariable;
78
+ /** Reference a variable, resolving it when this template is rendered. */
79
+ var(name: string): RegexTemplate;
80
+ }
81
+ /** Create a new regex designer with its own variable scope. */
82
+ export default function create(): RegexDesigner;
83
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ /** Check whether a value is a regex template. */
2
+ export function isRegexTemplate(value) {
3
+ return typeof value === 'function' && 'render' in value && typeof value.render === 'function';
4
+ }
5
+ function patternString(pattern, flags, compiling) {
6
+ if (pattern instanceof RegExp) {
7
+ const unicodeFlag = pattern.flags.includes('v') ? 'v' : pattern.flags.includes('u') ? 'u' : undefined;
8
+ if (compiling && unicodeFlag !== undefined && !flags?.includes(unicodeFlag)) {
9
+ throw new TypeError(`RegExp flag "${unicodeFlag}" must be passed when compiling the composed pattern`);
10
+ }
11
+ const scopedFlags = pattern.flags.replace(/[dgyuv]/g, '');
12
+ return scopedFlags === '' ? pattern.source : `(?${scopedFlags}:${pattern.source})`;
13
+ }
14
+ if (isRegexTemplate(pattern)) {
15
+ return pattern.render(flags, compiling);
16
+ }
17
+ return pattern.toString();
18
+ }
19
+ function template(render) {
20
+ const regex = (flags) => new RegExp(regex.render(flags, true), flags);
21
+ regex.render = (flags, compiling = false) => render(flags, compiling);
22
+ regex.toString = () => regex.render();
23
+ return regex;
24
+ }
25
+ function tpl(...patterns) {
26
+ return template((flags, compiling) => patterns.map(pattern => patternString(pattern, flags, compiling)).join(''));
27
+ }
28
+ function or(...patterns) {
29
+ return template((flags, compiling) => patterns.map(pattern => patternString(pattern, flags, compiling)).join('|'));
30
+ }
31
+ function group(options, ...patterns) {
32
+ if (typeof options !== 'boolean' && options.name !== undefined && options.capture !== undefined) {
33
+ throw new TypeError('Group options "name" and "capture" are mutually exclusive');
34
+ }
35
+ return template((flags, compiling) => {
36
+ const name = typeof options === 'boolean' ? undefined : options.name;
37
+ const capture = typeof options === 'boolean' ? options : options.capture ?? true;
38
+ const prefix = name !== undefined ? `?<${name}>` : capture ? '' : '?:';
39
+ return `(${prefix}${patterns.map(pattern => patternString(pattern, flags, compiling)).join('')})`;
40
+ });
41
+ }
42
+ function named(name, ...patterns) {
43
+ return group({ name }, ...patterns);
44
+ }
45
+ function opt(...patterns) {
46
+ return template((flags, compiling) => `${patternString(group(false, ...patterns), flags, compiling)}?`);
47
+ }
48
+ function qty(quantity, ...patterns) {
49
+ let min;
50
+ let max;
51
+ let greedy = true;
52
+ if (typeof quantity === 'number') {
53
+ min = quantity;
54
+ max = quantity;
55
+ }
56
+ else if (Array.isArray(quantity)) {
57
+ [min, max] = quantity;
58
+ }
59
+ else if ('exact' in quantity) {
60
+ min = quantity.exact;
61
+ max = quantity.exact;
62
+ greedy = quantity.greedy ?? true;
63
+ }
64
+ else {
65
+ min = quantity.min;
66
+ max = quantity.max;
67
+ greedy = quantity.greedy ?? true;
68
+ }
69
+ if (!Number.isInteger(min) || min < 0 || (max !== undefined && max !== Infinity && (!Number.isInteger(max) || max < min))) {
70
+ throw new RangeError('Quantity bounds must be non-negative integers with max >= min');
71
+ }
72
+ return template((flags, compiling) => `${patternString(group(false, ...patterns), flags, compiling)}{${min}${max === undefined || max === Infinity ? ',' : max === min ? '' : `,${max}`}}${greedy ? '' : '?'}`);
73
+ }
74
+ function look(prefix, positive, patterns) {
75
+ return template((flags, compiling) => `(${prefix}${positive ? '=' : '!'}${patterns.map(pattern => patternString(pattern, flags, compiling)).join('')})`);
76
+ }
77
+ function raw(...patterns) {
78
+ return template(() => patterns.map(p => RegExp.escape(p)).join(''));
79
+ }
80
+ /** Compose lazy regex templates and resolve reusable variables in an isolated scope. */
81
+ export class RegexDesigner {
82
+ variables = new Map();
83
+ resolvingVariables = [];
84
+ /** Concatenate patterns without adding groups or escaping. */
85
+ tpl(...patterns) {
86
+ return tpl(...patterns);
87
+ }
88
+ /** Join patterns with alternation without adding groups. */
89
+ or(...patterns) {
90
+ return or(...patterns);
91
+ }
92
+ /** Create a named capturing group around the concatenated patterns. */
93
+ named(name, ...patterns) {
94
+ return named(name, ...patterns);
95
+ }
96
+ /** Group patterns, optionally capturing them or assigning a capture name. */
97
+ group(options, ...patterns) {
98
+ return group(options, ...patterns);
99
+ }
100
+ /** Make the concatenated patterns optional using a non-capturing group. */
101
+ opt(...patterns) {
102
+ return opt(...patterns);
103
+ }
104
+ /** Repeat the concatenated patterns according to the supplied bounds. */
105
+ qty(quantity, ...patterns) {
106
+ return qty(quantity, ...patterns);
107
+ }
108
+ /** Escape literal text for use in a regex pattern. */
109
+ raw(...patterns) {
110
+ return raw(...patterns);
111
+ }
112
+ /** Create a positive or negative lookahead assertion. */
113
+ ahead(positive, ...patterns) {
114
+ return look('?', positive, patterns);
115
+ }
116
+ /** Create a positive lookahead assertion. */
117
+ ahead_pos(...patterns) {
118
+ return look('?', true, patterns);
119
+ }
120
+ /** Create a negative lookahead assertion. */
121
+ ahead_neg(...patterns) {
122
+ return look('?', false, patterns);
123
+ }
124
+ /** Create a positive or negative lookbehind assertion. */
125
+ behind(positive, ...patterns) {
126
+ return look('?<', positive, patterns);
127
+ }
128
+ /** Create a positive lookbehind assertion. */
129
+ behind_pos(...patterns) {
130
+ return look('?<', true, patterns);
131
+ }
132
+ /** Create a negative lookbehind assertion. */
133
+ behind_neg(...patterns) {
134
+ return look('?<', false, patterns);
135
+ }
136
+ /** Create a numeric or named backreference. */
137
+ ref(reference) {
138
+ return template(() => typeof reference === 'number' ? `\\${reference}` : `\\k<${reference}>`);
139
+ }
140
+ /** Define or replace a lazy pattern variable on this designer. */
141
+ set(name, ...patterns) {
142
+ this.variables.set(name, tpl(...patterns));
143
+ return this;
144
+ }
145
+ resolveVariable(name, flags, compiling) {
146
+ const cycleStart = this.resolvingVariables.indexOf(name);
147
+ if (cycleStart !== -1) {
148
+ const cycle = [...this.resolvingVariables.slice(cycleStart), name];
149
+ throw new Error(`Circular variable dependency: ${cycle.join(' -> ')}`);
150
+ }
151
+ const pattern = this.variables.get(name);
152
+ if (pattern === undefined) {
153
+ throw new Error(`Variable "${name}" is not set`);
154
+ }
155
+ this.resolvingVariables.push(name);
156
+ try {
157
+ return patternString(pattern, flags, compiling);
158
+ }
159
+ finally {
160
+ this.resolvingVariables.pop();
161
+ }
162
+ }
163
+ /** Reference a variable, resolving it when this template is rendered. */
164
+ var(name) {
165
+ return template((flags, compiling) => this.resolveVariable(name, flags, compiling));
166
+ }
167
+ }
168
+ /** Create a new regex designer with its own variable scope. */
169
+ export default function create() {
170
+ return new RegexDesigner();
171
+ }
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@regex-designer/js",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "A small TypeScript library for composing JavaScript regular expressions in a readable, reusable way.",
5
+ "private": false,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "regex",
10
+ "regexp",
11
+ "pattern",
12
+ "builder"
13
+ ],
14
+ "engines": {
15
+ "node": ">=24.0.0"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "LICENSE"
20
+ ],
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ }
27
+ },
28
+ "sideEffects": false,
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "typecheck": "npm run build && tsc -p tsconfig.test.json",
32
+ "lint": "eslint .",
33
+ "test": "npm run typecheck && node --test test/*.test.ts",
34
+ "check": "npm run lint && npm run test"
35
+ },
36
+ "devDependencies": {
37
+ "@eslint/js": "10.0.1",
38
+ "@types/node": "24.19.2",
39
+ "eslint": "10.12.0",
40
+ "globals": "17.13.0",
41
+ "typescript": "6.0.3",
42
+ "typescript-eslint": "8.71.1"
43
+ }
44
+ }