route-parser-ts 1.0.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) 2025 Marko Stijak (https://github.com/mstijak)
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 ADDED
@@ -0,0 +1,215 @@
1
+ # route-parser-ts
2
+
3
+ A TypeScript URL routing library that parses route patterns and matches them against URL paths. Supports named parameters, splat (wildcard) parameters, and optional segments.
4
+
5
+ Based on [route-parser](https://github.com/rcs/route-parser) by Ryan Sorensen.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install route-parser-ts
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import Route from 'route-parser-ts';
17
+
18
+ // Create a route
19
+ const route = Route('/users/:id');
20
+
21
+ // Match a path
22
+ const result = route.match('/users/123');
23
+ // => { id: '123' }
24
+
25
+ // Reverse a route
26
+ const path = route.reverse({ id: '456' });
27
+ // => '/users/456'
28
+ ```
29
+
30
+ ## Route Patterns
31
+
32
+ ### Static Paths
33
+
34
+ Match exact paths:
35
+
36
+ ```typescript
37
+ const route = Route('/foo');
38
+
39
+ route.match('/foo'); // => {}
40
+ route.match('/foo?query'); // => {} (query strings are ignored)
41
+ route.match('/bar'); // => false
42
+ route.match('/foobar'); // => false
43
+ ```
44
+
45
+ ### Named Parameters
46
+
47
+ Capture path segments with `:param` syntax:
48
+
49
+ ```typescript
50
+ const route = Route('/users/:id');
51
+
52
+ route.match('/users/123'); // => { id: '123' }
53
+ route.match('/users/abc'); // => { id: 'abc' }
54
+ route.match('/users/'); // => false
55
+ ```
56
+
57
+ Multiple parameters:
58
+
59
+ ```typescript
60
+ const route = Route('/users/:userId/posts/:postId');
61
+
62
+ route.match('/users/1/posts/42');
63
+ // => { userId: '1', postId: '42' }
64
+ ```
65
+
66
+ ### Splat Parameters
67
+
68
+ Capture multiple path segments with `*param` syntax:
69
+
70
+ ```typescript
71
+ const route = Route('/files/*path');
72
+
73
+ route.match('/files/images/photo.jpg');
74
+ // => { path: 'images/photo.jpg' }
75
+ ```
76
+
77
+ Multiple splats:
78
+
79
+ ```typescript
80
+ const route = Route('/*a/foo/*b');
81
+
82
+ route.match('/zoo/woo/foo/bar/baz');
83
+ // => { a: 'zoo/woo', b: 'bar/baz' }
84
+ ```
85
+
86
+ ### Mixed Parameters
87
+
88
+ Combine splats and named parameters:
89
+
90
+ ```typescript
91
+ const route = Route('/books/*section/:title');
92
+
93
+ route.match('/books/some/section/last-words-a-memoir');
94
+ // => { section: 'some/section', title: 'last-words-a-memoir' }
95
+ ```
96
+
97
+ ### Optional Segments
98
+
99
+ Make parts of the route optional with parentheses:
100
+
101
+ ```typescript
102
+ const route = Route('/users/:id(/style/:style)');
103
+
104
+ route.match('/users/3');
105
+ // => { id: '3', style: undefined }
106
+
107
+ route.match('/users/3/style/pirate');
108
+ // => { id: '3', style: 'pirate' }
109
+ ```
110
+
111
+ Optional segments starting with a word:
112
+
113
+ ```typescript
114
+ const route = Route('/things/(option/:first)');
115
+
116
+ route.match('/things/option/bar');
117
+ // => { first: 'bar' }
118
+ ```
119
+
120
+ Nested optional segments:
121
+
122
+ ```typescript
123
+ const route = Route('/users/:id(/style/:style(/more/:param))');
124
+
125
+ route.match('/users/3');
126
+ // => { id: '3', style: undefined, param: undefined }
127
+
128
+ route.match('/users/3/style/pirate');
129
+ // => { id: '3', style: 'pirate', param: undefined }
130
+
131
+ route.match('/users/3/style/pirate/more/things');
132
+ // => { id: '3', style: 'pirate', param: 'things' }
133
+ ```
134
+
135
+ ## API
136
+
137
+ ### `Route(spec: string)`
138
+
139
+ Creates a new route parser. Can be called with or without `new`.
140
+
141
+ ```typescript
142
+ const route1 = Route('/users/:id');
143
+ const route2 = new Route('/users/:id');
144
+ ```
145
+
146
+ Throws an error if `spec` is not provided.
147
+
148
+ ### `route.match(path: string): object | false`
149
+
150
+ Matches a path against the route pattern. Returns an object with the captured parameters on success, or `false` if the path doesn't match.
151
+
152
+ ```typescript
153
+ const route = Route('/users/:id');
154
+
155
+ route.match('/users/123'); // => { id: '123' }
156
+ route.match('/posts/123'); // => false
157
+ ```
158
+
159
+ ### `route.reverse(params?: object): string | false`
160
+
161
+ Generates a path from the route pattern and provided parameters. Returns the path string on success, or `false` if required parameters are missing.
162
+
163
+ ```typescript
164
+ const route = Route('/users/:id/posts/:postId');
165
+
166
+ route.reverse({ id: '1', postId: '42' });
167
+ // => '/users/1/posts/42'
168
+
169
+ route.reverse({ id: '1' });
170
+ // => false (missing required parameter)
171
+ ```
172
+
173
+ With optional segments:
174
+
175
+ ```typescript
176
+ const route = Route('/things/(option/:first)');
177
+
178
+ route.reverse({ first: 'bar' });
179
+ // => '/things/option/bar'
180
+
181
+ route.reverse();
182
+ // => '/things/' (optional segment omitted)
183
+ ```
184
+
185
+ Falsy values (like `0`) are handled correctly:
186
+
187
+ ```typescript
188
+ const route = Route('/items/:id?page=:page');
189
+
190
+ route.reverse({ id: 1, page: 0 });
191
+ // => '/items/1?page=0'
192
+ ```
193
+
194
+ ## TypeScript
195
+
196
+ The library is written in TypeScript and includes type definitions.
197
+
198
+ ```typescript
199
+ import Route, { RouteParams } from 'route-parser-ts';
200
+
201
+ const route = Route('/users/:id');
202
+ const params: RouteParams | false = route.match('/users/123');
203
+
204
+ if (params) {
205
+ console.log(params.id); // '123'
206
+ }
207
+ ```
208
+
209
+ ## Acknowledgments
210
+
211
+ This library is a TypeScript reimplementation of [route-parser](https://github.com/rcs/route-parser) by [Ryan Sorensen](https://github.com/rcs). The original library provides the same functionality in JavaScript.
212
+
213
+ ## License
214
+
215
+ MIT
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Route Parser - A TypeScript URL routing library
3
+ * Supports named parameters (:param), splats (*param), and optional segments (())
4
+ */
5
+ export interface RouteParams {
6
+ [key: string]: string | number | undefined;
7
+ }
8
+ /**
9
+ * RouteParser class
10
+ */
11
+ declare class RouteParser {
12
+ spec: string;
13
+ private tokens;
14
+ private regex;
15
+ private paramNames;
16
+ constructor(spec: string);
17
+ /**
18
+ * Match a path against this route
19
+ * Returns params object on match, false otherwise
20
+ */
21
+ match(path: string): RouteParams | false;
22
+ /**
23
+ * Reverse the route with given parameters
24
+ * Returns the path string on success, false otherwise
25
+ */
26
+ reverse(params?: RouteParams): string | false;
27
+ /**
28
+ * Check if required params (non-optional) can be fulfilled
29
+ */
30
+ private canFulfillRequired;
31
+ }
32
+ interface RouteParserConstructor {
33
+ new (spec: string): RouteParser;
34
+ (spec: string): RouteParser;
35
+ }
36
+ declare const Route: RouteParserConstructor;
37
+ export default Route;
38
+ export { Route, RouteParser };
package/dist/index.js ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Route Parser - A TypeScript URL routing library
3
+ * Supports named parameters (:param), splats (*param), and optional segments (())
4
+ */
5
+ /**
6
+ * Parse a route specification into tokens
7
+ */
8
+ function tokenize(spec) {
9
+ const tokens = [];
10
+ let i = 0;
11
+ while (i < spec.length) {
12
+ const char = spec[i];
13
+ if (char === ':') {
14
+ // Named parameter
15
+ i++;
16
+ let name = '';
17
+ while (i < spec.length && /[\w]/.test(spec[i])) {
18
+ name += spec[i];
19
+ i++;
20
+ }
21
+ if (name) {
22
+ tokens.push({ type: 'param', value: name });
23
+ }
24
+ }
25
+ else if (char === '*') {
26
+ // Splat parameter
27
+ i++;
28
+ let name = '';
29
+ while (i < spec.length && /[\w]/.test(spec[i])) {
30
+ name += spec[i];
31
+ i++;
32
+ }
33
+ if (name) {
34
+ tokens.push({ type: 'splat', value: name });
35
+ }
36
+ }
37
+ else if (char === '(') {
38
+ // Optional segment - find matching closing paren
39
+ i++;
40
+ let depth = 1;
41
+ let optionalContent = '';
42
+ while (i < spec.length && depth > 0) {
43
+ if (spec[i] === '(')
44
+ depth++;
45
+ else if (spec[i] === ')')
46
+ depth--;
47
+ if (depth > 0) {
48
+ optionalContent += spec[i];
49
+ }
50
+ i++;
51
+ }
52
+ // Recursively parse the optional content
53
+ const children = tokenize(optionalContent);
54
+ tokens.push({ type: 'optional', value: optionalContent, children });
55
+ }
56
+ else {
57
+ // Static text - consume until we hit a special character
58
+ let text = '';
59
+ while (i < spec.length && !':*()'.includes(spec[i])) {
60
+ text += spec[i];
61
+ i++;
62
+ }
63
+ if (text) {
64
+ tokens.push({ type: 'static', value: text });
65
+ }
66
+ }
67
+ }
68
+ return tokens;
69
+ }
70
+ /**
71
+ * Escape special regex characters in a string
72
+ */
73
+ function escapeRegex(str) {
74
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
75
+ }
76
+ /**
77
+ * Build a regex pattern from tokens
78
+ */
79
+ function buildRegex(tokens) {
80
+ let pattern = '';
81
+ const paramNames = [];
82
+ for (const token of tokens) {
83
+ switch (token.type) {
84
+ case 'static':
85
+ pattern += escapeRegex(token.value);
86
+ break;
87
+ case 'param':
88
+ // Named parameter matches one path segment (no slashes)
89
+ pattern += '([^/]+)';
90
+ paramNames.push(token.value);
91
+ break;
92
+ case 'splat':
93
+ // Splat matches any characters including slashes (non-greedy within constraints)
94
+ pattern += '(.+?)';
95
+ paramNames.push(token.value);
96
+ break;
97
+ case 'optional':
98
+ // Optional segment - recursively build and wrap in optional group
99
+ if (token.children) {
100
+ const { pattern: childPattern, paramNames: childNames } = buildRegex(token.children);
101
+ pattern += `(?:${childPattern})?`;
102
+ paramNames.push(...childNames);
103
+ }
104
+ break;
105
+ }
106
+ }
107
+ return { pattern, paramNames };
108
+ }
109
+ /**
110
+ * Attempt to reverse a route with given parameters
111
+ */
112
+ function reverseTokens(tokens, params) {
113
+ let result = '';
114
+ for (const token of tokens) {
115
+ switch (token.type) {
116
+ case 'static':
117
+ result += token.value;
118
+ break;
119
+ case 'param':
120
+ case 'splat': {
121
+ const value = params[token.value];
122
+ if (value === undefined || value === null || value === '') {
123
+ return false;
124
+ }
125
+ result += String(value);
126
+ break;
127
+ }
128
+ case 'optional':
129
+ if (token.children) {
130
+ // Try to fill the optional segment
131
+ const optionalResult = reverseTokens(token.children, params);
132
+ if (optionalResult !== false) {
133
+ result += optionalResult;
134
+ }
135
+ // If it fails, just skip the optional part (don't return false)
136
+ }
137
+ break;
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ /**
143
+ * Check if tokens can be fulfilled with the given params
144
+ */
145
+ function canFulfill(tokens, params) {
146
+ for (const token of tokens) {
147
+ if (token.type === 'param' || token.type === 'splat') {
148
+ const value = params[token.value];
149
+ if (value === undefined || value === null || value === '') {
150
+ return false;
151
+ }
152
+ }
153
+ else if (token.type === 'optional' && token.children) {
154
+ // Optional segments don't need to be fulfilled
155
+ continue;
156
+ }
157
+ }
158
+ return true;
159
+ }
160
+ /**
161
+ * RouteParser class
162
+ */
163
+ class RouteParser {
164
+ spec;
165
+ tokens;
166
+ regex;
167
+ paramNames;
168
+ constructor(spec) {
169
+ if (!spec) {
170
+ throw new Error('spec is required');
171
+ }
172
+ this.spec = spec;
173
+ this.tokens = tokenize(spec);
174
+ const { pattern, paramNames } = buildRegex(this.tokens);
175
+ // Match from start to end, with optional query string
176
+ this.regex = new RegExp(`^${pattern}(?:\\?.*)?$`);
177
+ this.paramNames = paramNames;
178
+ }
179
+ /**
180
+ * Match a path against this route
181
+ * Returns params object on match, false otherwise
182
+ */
183
+ match(path) {
184
+ const match = this.regex.exec(path);
185
+ if (!match) {
186
+ return false;
187
+ }
188
+ const params = {};
189
+ for (let i = 0; i < this.paramNames.length; i++) {
190
+ params[this.paramNames[i]] = match[i + 1];
191
+ }
192
+ return params;
193
+ }
194
+ /**
195
+ * Reverse the route with given parameters
196
+ * Returns the path string on success, false otherwise
197
+ */
198
+ reverse(params = {}) {
199
+ // Check if required (non-optional) params can be fulfilled
200
+ if (!this.canFulfillRequired(this.tokens, params)) {
201
+ return false;
202
+ }
203
+ return reverseTokens(this.tokens, params);
204
+ }
205
+ /**
206
+ * Check if required params (non-optional) can be fulfilled
207
+ */
208
+ canFulfillRequired(tokens, params) {
209
+ for (const token of tokens) {
210
+ if (token.type === 'param' || token.type === 'splat') {
211
+ const value = params[token.value];
212
+ if (value === undefined || value === null || value === '') {
213
+ return false;
214
+ }
215
+ }
216
+ // Optional segments don't need to be checked at top level
217
+ }
218
+ return true;
219
+ }
220
+ }
221
+ const RouteParserFactory = function (spec) {
222
+ if (this instanceof RouteParser) {
223
+ // Called with 'new'
224
+ return new RouteParser(spec);
225
+ }
226
+ // Called without 'new'
227
+ return new RouteParser(spec);
228
+ };
229
+ // Set the prototype so instanceof works
230
+ RouteParserFactory.prototype = RouteParser.prototype;
231
+ // Re-implement to allow both calling conventions
232
+ function createRouteParser(spec) {
233
+ return new RouteParser(spec);
234
+ }
235
+ // Export a wrapper that supports both calling conventions
236
+ const Route = function (spec) {
237
+ if (!(this instanceof Route)) {
238
+ return new Route(spec);
239
+ }
240
+ if (!spec) {
241
+ throw new Error('spec is required');
242
+ }
243
+ const parser = new RouteParser(spec);
244
+ // Copy properties to this instance
245
+ this.spec = parser.spec;
246
+ this.match = parser.match.bind(parser);
247
+ this.reverse = parser.reverse.bind(parser);
248
+ return this;
249
+ };
250
+ // Ensure prototype chain works for instanceof
251
+ Route.prototype = Object.create(Object.prototype);
252
+ Route.prototype.constructor = Route;
253
+ export default Route;
254
+ export { Route, RouteParser };
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,149 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Route from './index.js';
4
+ describe('Route', () => {
5
+ it('should create', () => {
6
+ assert.ok(Route('/foo'));
7
+ });
8
+ it('should create with new', () => {
9
+ assert.ok(new Route('/foo'));
10
+ });
11
+ it('should have proper prototype', () => {
12
+ const routeInstance = new Route('/foo');
13
+ assert.ok(routeInstance instanceof Route);
14
+ });
15
+ it('should throw on no spec', () => {
16
+ assert.throws(() => { Route(); }, /spec is required/);
17
+ });
18
+ describe('basic', () => {
19
+ it('should match /foo with a path of /foo', () => {
20
+ const route = Route('/foo');
21
+ assert.ok(route.match('/foo'));
22
+ });
23
+ it('should match /foo with a path of /foo?query', () => {
24
+ const route = Route('/foo');
25
+ assert.ok(route.match('/foo?query'));
26
+ });
27
+ it("shouldn't match /foo with a path of /bar/foo", () => {
28
+ const route = Route('/foo');
29
+ assert.strictEqual(route.match('/bar/foo'), false);
30
+ });
31
+ it("shouldn't match /foo with a path of /foobar", () => {
32
+ const route = Route('/foo');
33
+ assert.strictEqual(route.match('/foobar'), false);
34
+ });
35
+ it("shouldn't match /foo with a path of /bar", () => {
36
+ const route = Route('/foo');
37
+ assert.strictEqual(route.match('/bar'), false);
38
+ });
39
+ });
40
+ describe('basic parameters', () => {
41
+ it('should match /users/:id with a path of /users/1', () => {
42
+ const route = Route('/users/:id');
43
+ assert.ok(route.match('/users/1'));
44
+ });
45
+ it('should not match /users/:id with a path of /users/', () => {
46
+ const route = Route('/users/:id');
47
+ assert.strictEqual(route.match('/users/'), false);
48
+ });
49
+ it('should match /users/:id with a path of /users/1 and get parameters', () => {
50
+ const route = Route('/users/:id');
51
+ assert.deepStrictEqual(route.match('/users/1'), { id: '1' });
52
+ });
53
+ it('should match deep pathing and get parameters', () => {
54
+ const route = Route('/users/:id/comments/:comment/rating/:rating');
55
+ assert.deepStrictEqual(route.match('/users/1/comments/cats/rating/22222'), { id: '1', comment: 'cats', rating: '22222' });
56
+ });
57
+ });
58
+ describe('splat parameters', () => {
59
+ it('should handle double splat parameters', () => {
60
+ const route = Route('/*a/foo/*b');
61
+ assert.deepStrictEqual(route.match('/zoo/woo/foo/bar/baz'), { a: 'zoo/woo', b: 'bar/baz' });
62
+ });
63
+ });
64
+ describe('mixed', () => {
65
+ it('should handle mixed splat and named parameters', () => {
66
+ const route = Route('/books/*section/:title');
67
+ assert.deepStrictEqual(route.match('/books/some/section/last-words-a-memoir'), { section: 'some/section', title: 'last-words-a-memoir' });
68
+ });
69
+ });
70
+ describe('optional', () => {
71
+ it('should allow and match optional routes without optional part', () => {
72
+ const route = Route('/users/:id(/style/:style)');
73
+ assert.deepStrictEqual(route.match('/users/3'), { id: '3', style: undefined });
74
+ });
75
+ it('should allow and match optional routes with optional part', () => {
76
+ const route = Route('/users/:id(/style/:style)');
77
+ assert.deepStrictEqual(route.match('/users/3/style/pirate'), { id: '3', style: 'pirate' });
78
+ });
79
+ it('allows optional branches that start with a word character', () => {
80
+ const route = Route('/things/(option/:first)');
81
+ assert.deepStrictEqual(route.match('/things/option/bar'), { first: 'bar' });
82
+ });
83
+ describe('nested', () => {
84
+ it('allows nested', () => {
85
+ const route = Route('/users/:id(/style/:style(/more/:param))');
86
+ const result = route.match('/users/3/style/pirate');
87
+ const expected = { id: '3', style: 'pirate', param: undefined };
88
+ assert.deepStrictEqual(result, expected);
89
+ });
90
+ it('fetches the correct params from nested', () => {
91
+ const route = Route('/users/:id(/style/:style(/more/:param))');
92
+ assert.deepStrictEqual(route.match('/users/3/style/pirate/more/things'), { id: '3', style: 'pirate', param: 'things' });
93
+ });
94
+ });
95
+ });
96
+ describe('tilde prefix', () => {
97
+ it('should match routes starting with ~/', () => {
98
+ const route = Route('~/foo');
99
+ assert.ok(route.match('~/foo'));
100
+ });
101
+ it('should match ~/users/:id with parameters', () => {
102
+ const route = Route('~/users/:id');
103
+ assert.deepStrictEqual(route.match('~/users/123'), { id: '123' });
104
+ });
105
+ it('should reverse routes starting with ~/', () => {
106
+ const route = Route('~/users/:id');
107
+ assert.strictEqual(route.reverse({ id: '456' }), '~/users/456');
108
+ });
109
+ });
110
+ describe('reverse', () => {
111
+ it('reverses routes without params', () => {
112
+ const route = Route('/foo');
113
+ assert.strictEqual(route.reverse(), '/foo');
114
+ });
115
+ it('reverses routes with simple params', () => {
116
+ const route = Route('/:foo/:bar');
117
+ assert.strictEqual(route.reverse({ foo: '1', bar: '2' }), '/1/2');
118
+ });
119
+ it('reverses routes with optional params', () => {
120
+ const route = Route('/things/(option/:first)');
121
+ assert.strictEqual(route.reverse({ first: 'bar' }), '/things/option/bar');
122
+ });
123
+ it('reverses routes with unfilled optional params', () => {
124
+ const route = Route('/things/(option/:first)');
125
+ assert.strictEqual(route.reverse(), '/things/');
126
+ });
127
+ it("reverses routes with optional params that can't fulfill the optional branch", () => {
128
+ const route = Route('/things/(option/:first(/second/:second))');
129
+ assert.strictEqual(route.reverse({ second: 'foo' }), '/things/');
130
+ });
131
+ it("returns false for routes that can't be fulfilled", () => {
132
+ const route = Route('/foo/:bar');
133
+ assert.strictEqual(route.reverse({}), false);
134
+ });
135
+ it("returns false for routes with splat params that can't be fulfilled", () => {
136
+ const route = Route('/foo/*bar');
137
+ assert.strictEqual(route.reverse({}), false);
138
+ });
139
+ it('allows reversing falsy valued params', () => {
140
+ const path = '/account/json/wall/post/:id/comments/?start=:start&max=:max';
141
+ const vars = {
142
+ id: 50,
143
+ start: 0,
144
+ max: 12
145
+ };
146
+ assert.strictEqual(Route(path).reverse(vars), '/account/json/wall/post/50/comments/?start=0&max=12');
147
+ });
148
+ });
149
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "route-parser-ts",
3
+ "version": "1.0.0",
4
+ "description": "A TypeScript URL routing library with support for named parameters, splats, and optional segments",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "npm run build && node --test dist/test.js",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "route",
15
+ "router",
16
+ "url",
17
+ "parser",
18
+ "typescript"
19
+ ],
20
+ "license": "MIT",
21
+ "devDependencies": {
22
+ "@types/node": "^25.0.3",
23
+ "typescript": "^5.3.0"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ]
28
+ }