@typed/router 0.31.0 → 1.0.0-beta.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/README.md +111 -2
- package/dist/AST.d.ts +96 -0
- package/dist/AST.d.ts.map +1 -0
- package/dist/AST.js +32 -0
- package/dist/CurrentRoute.d.ts +18 -0
- package/dist/CurrentRoute.d.ts.map +1 -0
- package/dist/CurrentRoute.js +18 -0
- package/dist/Matcher.d.ts +191 -0
- package/dist/Matcher.d.ts.map +1 -0
- package/dist/Matcher.js +597 -0
- package/dist/Parser.d.ts +96 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +1 -0
- package/dist/Path.d.ts +216 -0
- package/dist/Path.d.ts.map +1 -0
- package/dist/Path.js +248 -0
- package/dist/Route.d.ts +57 -0
- package/dist/Route.d.ts.map +1 -0
- package/dist/Route.js +151 -0
- package/dist/Router.d.ts +9 -0
- package/dist/Router.d.ts.map +1 -0
- package/dist/Router.js +8 -0
- package/dist/Uri.d.ts +115 -0
- package/dist/Uri.d.ts.map +1 -0
- package/dist/Uri.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/package.json +25 -69
- package/src/AST.ts +166 -0
- package/src/CurrentRoute.ts +30 -331
- package/src/Matcher.test.ts +476 -0
- package/src/Matcher.ts +1269 -328
- package/src/Parser.ts +282 -0
- package/src/Path.test.ts +318 -0
- package/src/Path.ts +691 -0
- package/src/Route.test.ts +268 -0
- package/src/Route.ts +316 -0
- package/src/Router.ts +31 -0
- package/src/Uri.ts +214 -0
- package/src/index.ts +4 -28
- package/tsconfig.json +6 -0
- package/CurrentRoute/package.json +0 -6
- package/LICENSE +0 -21
- package/MatchInput/package.json +0 -6
- package/Matcher/package.json +0 -6
- package/RouteGuard/package.json +0 -6
- package/RouteMatch/package.json +0 -6
- package/dist/cjs/CurrentRoute.js +0 -170
- package/dist/cjs/CurrentRoute.js.map +0 -1
- package/dist/cjs/MatchInput.js +0 -96
- package/dist/cjs/MatchInput.js.map +0 -1
- package/dist/cjs/Matcher.js +0 -138
- package/dist/cjs/Matcher.js.map +0 -1
- package/dist/cjs/RouteGuard.js +0 -78
- package/dist/cjs/RouteGuard.js.map +0 -1
- package/dist/cjs/RouteMatch.js +0 -49
- package/dist/cjs/RouteMatch.js.map +0 -1
- package/dist/cjs/index.js +0 -53
- package/dist/cjs/index.js.map +0 -1
- package/dist/dts/CurrentRoute.d.ts +0 -94
- package/dist/dts/CurrentRoute.d.ts.map +0 -1
- package/dist/dts/MatchInput.d.ts +0 -135
- package/dist/dts/MatchInput.d.ts.map +0 -1
- package/dist/dts/Matcher.d.ts +0 -121
- package/dist/dts/Matcher.d.ts.map +0 -1
- package/dist/dts/RouteGuard.d.ts +0 -94
- package/dist/dts/RouteGuard.d.ts.map +0 -1
- package/dist/dts/RouteMatch.d.ts +0 -50
- package/dist/dts/RouteMatch.d.ts.map +0 -1
- package/dist/dts/index.d.ts +0 -24
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/esm/CurrentRoute.js +0 -152
- package/dist/esm/CurrentRoute.js.map +0 -1
- package/dist/esm/MatchInput.js +0 -79
- package/dist/esm/MatchInput.js.map +0 -1
- package/dist/esm/Matcher.js +0 -130
- package/dist/esm/Matcher.js.map +0 -1
- package/dist/esm/RouteGuard.js +0 -57
- package/dist/esm/RouteGuard.js.map +0 -1
- package/dist/esm/RouteMatch.js +0 -29
- package/dist/esm/RouteMatch.js.map +0 -1
- package/dist/esm/index.js +0 -24
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/package.json +0 -4
- package/src/MatchInput.ts +0 -282
- package/src/RouteGuard.ts +0 -217
- package/src/RouteMatch.ts +0 -104
package/src/Path.ts
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import type { Arg0, TypeLambda1 } from "hkt-core";
|
|
2
|
+
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import type { PathAst } from "./AST.js";
|
|
5
|
+
import * as AST from "./AST.js";
|
|
6
|
+
import type { Parser } from "./Parser.js";
|
|
7
|
+
|
|
8
|
+
type PathStopChar = "/" | ":" | "*" | "?";
|
|
9
|
+
type QueryValueStopChar = "&";
|
|
10
|
+
|
|
11
|
+
type TakeWhileNotInternal<
|
|
12
|
+
Input extends string,
|
|
13
|
+
Stop extends string,
|
|
14
|
+
Acc extends string = "",
|
|
15
|
+
> = Input extends `${infer Head}${infer Tail}`
|
|
16
|
+
? Head extends Stop
|
|
17
|
+
? readonly [Acc, Input]
|
|
18
|
+
: TakeWhileNotInternal<Tail, Stop, `${Acc}${Head}`>
|
|
19
|
+
: readonly [Acc, Input];
|
|
20
|
+
|
|
21
|
+
type TakeWhileNot1Internal<Input extends string, Stop extends string> =
|
|
22
|
+
TakeWhileNotInternal<Input, Stop> extends readonly [
|
|
23
|
+
infer Taken extends string,
|
|
24
|
+
infer Rest extends string,
|
|
25
|
+
]
|
|
26
|
+
? Taken extends ""
|
|
27
|
+
? never
|
|
28
|
+
: readonly [Taken, Rest]
|
|
29
|
+
: never;
|
|
30
|
+
|
|
31
|
+
interface TakeWhileNot1<Stop extends string> extends Parser<string> {
|
|
32
|
+
readonly return: Arg0<this> extends infer Input extends string
|
|
33
|
+
? TakeWhileNot1Internal<Input, Stop>
|
|
34
|
+
: never;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Second extends TypeLambda1 {
|
|
38
|
+
readonly return: Arg0<this> extends readonly [infer _A, infer B] ? B : never;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface RegexBetweenParens extends TypeLambda1 {
|
|
42
|
+
readonly return: Arg0<this> extends readonly ["(", readonly [infer Regex extends string, ")"]]
|
|
43
|
+
? Regex
|
|
44
|
+
: never;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type RegexParser = Parser.Map<
|
|
48
|
+
Parser.Zip<Parser.Char<"(">, Parser.Zip<TakeWhileNot1<")">, Parser.Char<")">>>,
|
|
49
|
+
RegexBetweenParens
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
type OptionalRegexParser = Parser.Optional<RegexParser>;
|
|
53
|
+
type OptionalQuestionMarkParser = Parser.Optional<Parser.Char<"?">>;
|
|
54
|
+
|
|
55
|
+
type ParameterNameParser = Parser.Zip<Parser.Char<":">, Parser.TakeWhile1<Parser.AlphaNumeric>>;
|
|
56
|
+
|
|
57
|
+
type ParameterPartsParser = Parser.Zip<
|
|
58
|
+
ParameterNameParser,
|
|
59
|
+
Parser.Zip<OptionalRegexParser, OptionalQuestionMarkParser>
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
type ParameterAst<
|
|
63
|
+
Name extends string,
|
|
64
|
+
Regex extends string | undefined,
|
|
65
|
+
OptionalMark extends "?" | undefined,
|
|
66
|
+
> = [
|
|
67
|
+
{ readonly type: "parameter"; readonly name: Name } & ([Regex] extends [string]
|
|
68
|
+
? { regex: Regex }
|
|
69
|
+
: {}) &
|
|
70
|
+
([OptionalMark] extends ["?"] ? { optional: true } : {}),
|
|
71
|
+
] extends [infer Ast]
|
|
72
|
+
? ToReadonlyRecord<Ast>
|
|
73
|
+
: never;
|
|
74
|
+
|
|
75
|
+
interface ToParameterAst extends TypeLambda1 {
|
|
76
|
+
readonly return: Arg0<this> extends readonly [
|
|
77
|
+
readonly [":", infer Name extends string],
|
|
78
|
+
readonly [infer Regex, infer OptionalMark],
|
|
79
|
+
]
|
|
80
|
+
? ParameterAst<
|
|
81
|
+
Name,
|
|
82
|
+
Regex extends string ? Regex : undefined,
|
|
83
|
+
OptionalMark extends "?" ? OptionalMark : undefined
|
|
84
|
+
>
|
|
85
|
+
: never;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type ParameterParser = Parser.Map<ParameterPartsParser, ToParameterAst>;
|
|
89
|
+
|
|
90
|
+
interface ToWildcardAst extends TypeLambda1 {
|
|
91
|
+
readonly return: { readonly type: "wildcard" };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type WildcardParser = Parser.Map<Parser.Char<"*">, ToWildcardAst>;
|
|
95
|
+
|
|
96
|
+
interface ToLiteralAst extends TypeLambda1 {
|
|
97
|
+
readonly return: Arg0<this> extends infer Value extends string
|
|
98
|
+
? { readonly type: "literal"; readonly value: Value }
|
|
99
|
+
: never;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type PathLiteralParser = Parser.Map<TakeWhileNot1<PathStopChar>, ToLiteralAst>;
|
|
103
|
+
|
|
104
|
+
type QueryLiteralParser = Parser.Map<TakeWhileNot1<QueryValueStopChar>, ToLiteralAst>;
|
|
105
|
+
|
|
106
|
+
type QueryValueParser = Parser.OrElse<
|
|
107
|
+
ParameterParser,
|
|
108
|
+
Parser.OrElse<WildcardParser, QueryLiteralParser>
|
|
109
|
+
>;
|
|
110
|
+
|
|
111
|
+
interface ToQueryParamAst extends TypeLambda1 {
|
|
112
|
+
readonly return: Arg0<this> extends readonly [
|
|
113
|
+
infer Name extends string,
|
|
114
|
+
readonly ["=", infer Value extends PathAst],
|
|
115
|
+
]
|
|
116
|
+
? { readonly type: "query-param"; readonly name: Name; readonly value: Value }
|
|
117
|
+
: never;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type QueryParamParser = Parser.Map<
|
|
121
|
+
Parser.Zip<
|
|
122
|
+
Parser.TakeWhile1<Parser.AlphaNumeric>,
|
|
123
|
+
Parser.Zip<Parser.Char<"=">, QueryValueParser>
|
|
124
|
+
>,
|
|
125
|
+
ToQueryParamAst
|
|
126
|
+
>;
|
|
127
|
+
|
|
128
|
+
type QueryParamTailParser = Parser.Map<Parser.Zip<Parser.Char<"&">, QueryParamParser>, Second>;
|
|
129
|
+
|
|
130
|
+
interface PrependToTuple extends TypeLambda1 {
|
|
131
|
+
readonly return: Arg0<this> extends readonly [
|
|
132
|
+
infer Head,
|
|
133
|
+
infer Tail extends ReadonlyArray<unknown>,
|
|
134
|
+
]
|
|
135
|
+
? readonly [Head, ...Tail]
|
|
136
|
+
: never;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
type QueryParamListParser = Parser.Map<
|
|
140
|
+
Parser.Zip<QueryParamParser, Parser.Many<QueryParamTailParser>>,
|
|
141
|
+
PrependToTuple
|
|
142
|
+
>;
|
|
143
|
+
|
|
144
|
+
interface ToQueryParamsAst extends TypeLambda1 {
|
|
145
|
+
readonly return: Arg0<this> extends readonly [
|
|
146
|
+
"?",
|
|
147
|
+
infer Params extends ReadonlyArray<PathAst.QueryParam>,
|
|
148
|
+
]
|
|
149
|
+
? {
|
|
150
|
+
readonly type: "query-params";
|
|
151
|
+
readonly value: Params;
|
|
152
|
+
}
|
|
153
|
+
: never;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type QueryParamsParser = Parser.Map<
|
|
157
|
+
Parser.Zip<Parser.Char<"?">, QueryParamListParser>,
|
|
158
|
+
ToQueryParamsAst
|
|
159
|
+
>;
|
|
160
|
+
|
|
161
|
+
type PathAtomParser = Parser.OrElse<
|
|
162
|
+
QueryParamsParser,
|
|
163
|
+
Parser.OrElse<ParameterParser, Parser.OrElse<WildcardParser, PathLiteralParser>>
|
|
164
|
+
>;
|
|
165
|
+
|
|
166
|
+
type SkipSlashesParser = Parser.Optional<Parser.Many1<Parser.Char<"/">>>;
|
|
167
|
+
|
|
168
|
+
export type PathParser = Parser.Map<Parser.Zip<SkipSlashesParser, PathAtomParser>, Second>;
|
|
169
|
+
|
|
170
|
+
type SlashAst = { readonly type: "slash" };
|
|
171
|
+
|
|
172
|
+
type PathChunk = readonly [PathAst] | readonly [SlashAst, PathAst];
|
|
173
|
+
|
|
174
|
+
interface ToPathChunk extends TypeLambda1 {
|
|
175
|
+
readonly return: Arg0<this> extends readonly [infer Slashes, infer Ast extends PathAst]
|
|
176
|
+
? [Slashes] extends [undefined]
|
|
177
|
+
? readonly [Ast]
|
|
178
|
+
: readonly [SlashAst, Ast]
|
|
179
|
+
: never;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
type PathChunkParser = Parser.Map<Parser.Zip<SkipSlashesParser, PathAtomParser>, ToPathChunk>;
|
|
183
|
+
|
|
184
|
+
type FlattenChunks<
|
|
185
|
+
Chunks extends ReadonlyArray<PathChunk>,
|
|
186
|
+
Acc extends ReadonlyArray<PathAst> = readonly [],
|
|
187
|
+
> = Chunks extends readonly [
|
|
188
|
+
infer Head extends PathChunk,
|
|
189
|
+
...infer Tail extends ReadonlyArray<PathChunk>,
|
|
190
|
+
]
|
|
191
|
+
? FlattenChunks<Tail, readonly [...Acc, ...Head]>
|
|
192
|
+
: Acc;
|
|
193
|
+
|
|
194
|
+
type CombineChunks<First, Chunks extends ReadonlyArray<PathChunk>> = [First] extends [PathAst]
|
|
195
|
+
? readonly [First, ...FlattenChunks<Chunks>]
|
|
196
|
+
: FlattenChunks<Chunks>;
|
|
197
|
+
|
|
198
|
+
interface ToAsts extends TypeLambda1 {
|
|
199
|
+
readonly return: Arg0<this> extends readonly [
|
|
200
|
+
infer First,
|
|
201
|
+
infer Chunks extends ReadonlyArray<PathChunk>,
|
|
202
|
+
]
|
|
203
|
+
? CombineChunks<First, Chunks>
|
|
204
|
+
: never;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
type ParseAstsResult<Input extends string> = Parser.Run<
|
|
208
|
+
Parser.Map<Parser.Zip<Parser.Optional<PathParser>, Parser.Many<PathChunkParser>>, ToAsts>,
|
|
209
|
+
Input
|
|
210
|
+
>;
|
|
211
|
+
|
|
212
|
+
type GetAsts<R> = [R] extends [never]
|
|
213
|
+
? never
|
|
214
|
+
: R extends readonly [infer Asts extends ReadonlyArray<PathAst>, infer _Rest extends string]
|
|
215
|
+
? Asts
|
|
216
|
+
: never;
|
|
217
|
+
|
|
218
|
+
export type ParseAsts<Input extends string> = GetAsts<ParseAstsResult<Input>>;
|
|
219
|
+
|
|
220
|
+
type ParamsOfAst<T> = T extends {
|
|
221
|
+
type: "parameter";
|
|
222
|
+
name: infer Name extends string;
|
|
223
|
+
optional: true;
|
|
224
|
+
}
|
|
225
|
+
? { [K in Name]?: string }
|
|
226
|
+
: T extends { readonly type: "parameter"; readonly name: infer Name extends string }
|
|
227
|
+
? { [K in Name]: string }
|
|
228
|
+
: T extends { readonly type: "wildcard" }
|
|
229
|
+
? { "*": string }
|
|
230
|
+
: T extends {
|
|
231
|
+
readonly type: "query-params";
|
|
232
|
+
readonly value: infer Values extends ReadonlyArray<PathAst.QueryParam>;
|
|
233
|
+
}
|
|
234
|
+
? ParamsOfQueryParams<Values>
|
|
235
|
+
: {};
|
|
236
|
+
|
|
237
|
+
type ParamsOfQueryParams<
|
|
238
|
+
T extends ReadonlyArray<PathAst.QueryParam>,
|
|
239
|
+
Acc = {},
|
|
240
|
+
> = T extends readonly [infer Head, ...infer Tail extends ReadonlyArray<PathAst.QueryParam>]
|
|
241
|
+
? ParamsOfQueryParams<Tail, Acc & ParamsOfQueryParam<Head>>
|
|
242
|
+
: Acc;
|
|
243
|
+
|
|
244
|
+
type ParamsOfQueryParam<T> = T extends {
|
|
245
|
+
readonly type: "query-param";
|
|
246
|
+
readonly value: infer Value extends PathAst;
|
|
247
|
+
}
|
|
248
|
+
? ParamsOfAst<Value>
|
|
249
|
+
: {};
|
|
250
|
+
|
|
251
|
+
type GetParams<T extends ReadonlyArray<PathAst>, Acc = {}> = T extends readonly [
|
|
252
|
+
infer Head,
|
|
253
|
+
...infer Tail extends ReadonlyArray<PathAst>,
|
|
254
|
+
]
|
|
255
|
+
? GetParams<Tail, Acc & ParamsOfAst<Head>>
|
|
256
|
+
: Acc;
|
|
257
|
+
|
|
258
|
+
type PathParamsOfAst<T> = T extends {
|
|
259
|
+
type: "parameter";
|
|
260
|
+
name: infer Name extends string;
|
|
261
|
+
optional: true;
|
|
262
|
+
}
|
|
263
|
+
? { [K in Name]?: string }
|
|
264
|
+
: T extends { readonly type: "parameter"; readonly name: infer Name extends string }
|
|
265
|
+
? { [K in Name]: string }
|
|
266
|
+
: T extends { readonly type: "wildcard" }
|
|
267
|
+
? { "*": string }
|
|
268
|
+
: {};
|
|
269
|
+
|
|
270
|
+
type QueryParamsOfAst<T> = T extends {
|
|
271
|
+
readonly type: "query-params";
|
|
272
|
+
readonly value: infer Values extends ReadonlyArray<PathAst.QueryParam>;
|
|
273
|
+
}
|
|
274
|
+
? ParamsOfQueryParams<Values>
|
|
275
|
+
: {};
|
|
276
|
+
|
|
277
|
+
type GetPathParams<T extends ReadonlyArray<PathAst>, Acc = {}> = T extends readonly [
|
|
278
|
+
infer Head,
|
|
279
|
+
...infer Tail extends ReadonlyArray<PathAst>,
|
|
280
|
+
]
|
|
281
|
+
? GetPathParams<Tail, Acc & PathParamsOfAst<Head>>
|
|
282
|
+
: Acc;
|
|
283
|
+
|
|
284
|
+
type GetQueryParams<T extends ReadonlyArray<PathAst>, Acc = {}> = T extends readonly [
|
|
285
|
+
infer Head,
|
|
286
|
+
...infer Tail extends ReadonlyArray<PathAst>,
|
|
287
|
+
]
|
|
288
|
+
? GetQueryParams<Tail, Acc & QueryParamsOfAst<Head>>
|
|
289
|
+
: Acc;
|
|
290
|
+
|
|
291
|
+
type ToReadonlyRecord<T> = [T] extends [infer T2] ? { readonly [K in keyof T2]: T2[K] } : never;
|
|
292
|
+
|
|
293
|
+
export type PathParams<P extends string> =
|
|
294
|
+
ParseAsts<P> extends infer Asts
|
|
295
|
+
? [Asts] extends [never]
|
|
296
|
+
? never
|
|
297
|
+
: Asts extends ReadonlyArray<PathAst>
|
|
298
|
+
? ToReadonlyRecord<GetPathParams<Asts>>
|
|
299
|
+
: never
|
|
300
|
+
: never;
|
|
301
|
+
|
|
302
|
+
export type QueryParams<P extends string> =
|
|
303
|
+
ParseAsts<P> extends infer Asts
|
|
304
|
+
? [Asts] extends [never]
|
|
305
|
+
? never
|
|
306
|
+
: Asts extends ReadonlyArray<PathAst>
|
|
307
|
+
? ToReadonlyRecord<GetQueryParams<Asts>>
|
|
308
|
+
: never
|
|
309
|
+
: never;
|
|
310
|
+
|
|
311
|
+
export type Params<P extends string> =
|
|
312
|
+
ParseAsts<P> extends infer Asts
|
|
313
|
+
? [Asts] extends [never]
|
|
314
|
+
? never
|
|
315
|
+
: Asts extends ReadonlyArray<PathAst>
|
|
316
|
+
? ToReadonlyRecord<GetParams<Asts>>
|
|
317
|
+
: never
|
|
318
|
+
: never;
|
|
319
|
+
|
|
320
|
+
export type RuntimeParseResult = readonly [asts: ReadonlyArray<PathAst>, rest: string];
|
|
321
|
+
|
|
322
|
+
export function parseWithRest(input: string): RuntimeParseResult {
|
|
323
|
+
let index = 0;
|
|
324
|
+
const asts: Array<PathAst> = [];
|
|
325
|
+
|
|
326
|
+
while (index < input.length) {
|
|
327
|
+
const start = index;
|
|
328
|
+
let hasSlash = false;
|
|
329
|
+
while (index < input.length && input[index] === "/") {
|
|
330
|
+
hasSlash = true;
|
|
331
|
+
index++;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const atom = parseAtom(input, index);
|
|
335
|
+
if (atom === undefined) {
|
|
336
|
+
index = start;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (hasSlash && asts.length > 0) {
|
|
341
|
+
asts.push(AST.slash());
|
|
342
|
+
}
|
|
343
|
+
asts.push(atom.ast);
|
|
344
|
+
index = atom.index;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return [asts, input.slice(index)];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function parse<const P extends string>(input: P): ParseAsts<P> {
|
|
351
|
+
const [asts, rest] = parseWithRest(input);
|
|
352
|
+
for (let i = 0; i < rest.length; i++) {
|
|
353
|
+
if (rest[i] !== "/") {
|
|
354
|
+
const index = input.length - rest.length;
|
|
355
|
+
throw new Error(`Failed to parse path at index ${index}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return asts as ParseAsts<P>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export type Join<Parts extends ReadonlyArray<PathAst>> = `/${StringJoin<
|
|
362
|
+
{
|
|
363
|
+
[K in keyof Parts]: FormatAst<Parts[K]>;
|
|
364
|
+
},
|
|
365
|
+
""
|
|
366
|
+
>}`;
|
|
367
|
+
|
|
368
|
+
type StringJoin<
|
|
369
|
+
Input extends ReadonlyArray<string>,
|
|
370
|
+
R extends string = "",
|
|
371
|
+
> = Input extends readonly [infer A extends string, ...infer Rest extends ReadonlyArray<string>]
|
|
372
|
+
? StringJoin<Rest, `${R}${A}`>
|
|
373
|
+
: R;
|
|
374
|
+
|
|
375
|
+
type FormatAst<T extends PathAst> = [T] extends [PathAst.Literal]
|
|
376
|
+
? T["value"]
|
|
377
|
+
: [T] extends [PathAst.Parameter]
|
|
378
|
+
? FormatParameterAst<T>
|
|
379
|
+
: [T] extends [PathAst.Wildcard]
|
|
380
|
+
? `*`
|
|
381
|
+
: [T] extends [PathAst.Slash]
|
|
382
|
+
? `/`
|
|
383
|
+
: [T] extends [PathAst.QueryParams]
|
|
384
|
+
? FormatQueryParamsAst<T["value"]>
|
|
385
|
+
: never;
|
|
386
|
+
|
|
387
|
+
type FormatParameterAst<T extends PathAst.Parameter> =
|
|
388
|
+
`:${T["name"]}${T["optional"] extends true ? "?" : ""}`;
|
|
389
|
+
type FormatQueryParamsAst<
|
|
390
|
+
T extends ReadonlyArray<PathAst.QueryParam>,
|
|
391
|
+
R extends string = "?",
|
|
392
|
+
> = T extends readonly [
|
|
393
|
+
infer Head extends PathAst.QueryParam,
|
|
394
|
+
...infer Tail extends ReadonlyArray<PathAst.QueryParam>,
|
|
395
|
+
]
|
|
396
|
+
? FormatQueryParamsAst<Tail, `${R}${R extends "?" ? "" : "&"}${FormatQueryParamAst<Head>}`>
|
|
397
|
+
: R;
|
|
398
|
+
|
|
399
|
+
type FormatQueryParamAst<T extends PathAst.QueryParam> = `${T["name"]}=${FormatAst<T["value"]>}`;
|
|
400
|
+
|
|
401
|
+
export function join<const Parts extends ReadonlyArray<PathAst>>(asts: Parts): Join<Parts> {
|
|
402
|
+
return `/${asts.map(formatAst).join("")}` as Join<Parts>;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function formatAst(ast: PathAst): string {
|
|
406
|
+
switch (ast.type) {
|
|
407
|
+
case "literal":
|
|
408
|
+
return ast.value;
|
|
409
|
+
case "parameter":
|
|
410
|
+
return `:${ast.name}`;
|
|
411
|
+
case "wildcard":
|
|
412
|
+
return "*";
|
|
413
|
+
case "slash":
|
|
414
|
+
return "/";
|
|
415
|
+
case "query-params":
|
|
416
|
+
return `?${ast.value.map(getQueryParamAst).join("&")}`;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getQueryParamAst(ast: PathAst.QueryParam): string {
|
|
421
|
+
return `${ast.name}=${formatAst(ast.value)}`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
type Atom = {
|
|
425
|
+
readonly ast: PathAst;
|
|
426
|
+
readonly index: number;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
function parseAtom(input: string, index: number): Atom | undefined {
|
|
430
|
+
const char = input[index];
|
|
431
|
+
|
|
432
|
+
if (char === undefined) {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (char === "?") {
|
|
437
|
+
return parseQueryParams(input, index);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (char === ":") {
|
|
441
|
+
return parseParameter(input, index);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (char === "*") {
|
|
445
|
+
return { ast: AST.wildcard(), index: index + 1 };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return parsePathLiteral(input, index);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function parseParameter(input: string, index: number): Atom | undefined {
|
|
452
|
+
if (input[index] !== ":") {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
let i = index + 1;
|
|
457
|
+
let name = "";
|
|
458
|
+
while (i < input.length && isAlphaNumeric(input[i])) {
|
|
459
|
+
name += input[i];
|
|
460
|
+
i++;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (name.length === 0) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let regex: string | undefined = undefined;
|
|
468
|
+
if (input[i] === "(") {
|
|
469
|
+
i++;
|
|
470
|
+
const start = i;
|
|
471
|
+
while (i < input.length && input[i] !== ")") {
|
|
472
|
+
i++;
|
|
473
|
+
}
|
|
474
|
+
if (i >= input.length) {
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
if (i === start) {
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
regex = input.slice(start, i);
|
|
481
|
+
i++;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
let optional: true | undefined = undefined;
|
|
485
|
+
if (input[i] === "?") {
|
|
486
|
+
optional = true;
|
|
487
|
+
i++;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return { ast: AST.parameter(name, optional, regex), index: i };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function parsePathLiteral(input: string, index: number): Atom | undefined {
|
|
494
|
+
const char = input[index];
|
|
495
|
+
if (char === undefined || isPathStopChar(char)) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let i = index;
|
|
500
|
+
while (i < input.length && !isPathStopChar(input[i])) {
|
|
501
|
+
i++;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return { ast: AST.literal(input.slice(index, i)), index: i };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function parseQueryParams(input: string, index: number): Atom | undefined {
|
|
508
|
+
if (input[index] !== "?") {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const first = parseQueryParam(input, index + 1);
|
|
513
|
+
if (first === undefined) {
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
let i = first.index;
|
|
518
|
+
const params: Array<PathAst.QueryParam> = [first.ast];
|
|
519
|
+
|
|
520
|
+
while (i < input.length && input[i] === "&") {
|
|
521
|
+
const start = i;
|
|
522
|
+
const next = parseQueryParam(input, i + 1);
|
|
523
|
+
if (next === undefined) {
|
|
524
|
+
i = start;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
params.push(next.ast);
|
|
528
|
+
i = next.index;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return { ast: AST.queryParams(params), index: i };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
type QueryParamResult = {
|
|
535
|
+
readonly ast: PathAst.QueryParam;
|
|
536
|
+
readonly index: number;
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
function parseQueryParam(input: string, index: number): QueryParamResult | undefined {
|
|
540
|
+
let i = index;
|
|
541
|
+
let name = "";
|
|
542
|
+
while (i < input.length && isAlphaNumeric(input[i])) {
|
|
543
|
+
name += input[i];
|
|
544
|
+
i++;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (name.length === 0) {
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (input[i] !== "=") {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
i++;
|
|
555
|
+
|
|
556
|
+
const value = parseQueryValue(input, i);
|
|
557
|
+
if (value === undefined) {
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return { ast: AST.queryParam(name, value.ast), index: value.index };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function parseQueryValue(input: string, index: number): Atom | undefined {
|
|
565
|
+
const char = input[index];
|
|
566
|
+
|
|
567
|
+
if (char === undefined) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (char === ":") {
|
|
572
|
+
return parseParameter(input, index);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (char === "*") {
|
|
576
|
+
return { ast: AST.wildcard(), index: index + 1 };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
let i = index;
|
|
580
|
+
while (i < input.length && input[i] !== "&") {
|
|
581
|
+
i++;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (i === index) {
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return { ast: AST.literal(input.slice(index, i)), index: i };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function isPathStopChar(char: string): boolean {
|
|
592
|
+
return char === "/" || char === ":" || char === "*" || char === "?";
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function isAlphaNumeric(char: string): boolean {
|
|
596
|
+
return (
|
|
597
|
+
(char >= "0" && char <= "9") || (char >= "a" && char <= "z") || (char >= "A" && char <= "Z")
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @internal
|
|
603
|
+
*/
|
|
604
|
+
export function getSchemaFields<const Parts extends ReadonlyArray<PathAst>>(parts: Parts) {
|
|
605
|
+
const requiredFields: Array<[string, Schema.Top]> = [];
|
|
606
|
+
const optionalFields: Array<[Schema.Record.Key, Schema.Top]> = [];
|
|
607
|
+
const queryParams: Array<
|
|
608
|
+
[
|
|
609
|
+
string,
|
|
610
|
+
{
|
|
611
|
+
readonly requiredFields: Array<[string, Schema.Top]>;
|
|
612
|
+
readonly optionalFields: Array<[Schema.Record.Key, Schema.Top]>;
|
|
613
|
+
},
|
|
614
|
+
]
|
|
615
|
+
> = [];
|
|
616
|
+
|
|
617
|
+
function addParameter(param: PathAst.Parameter) {
|
|
618
|
+
const base = param.regex
|
|
619
|
+
? Schema.String.pipe(Schema.check(Schema.isPattern(new RegExp(param.regex))))
|
|
620
|
+
: Schema.String;
|
|
621
|
+
|
|
622
|
+
if (param.optional) {
|
|
623
|
+
optionalFields.push([Schema.optionalKey(Schema.Literal(param.name)), Schema.optional(base)]);
|
|
624
|
+
} else {
|
|
625
|
+
requiredFields.push([param.name, base]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function addQueryParams(params: ReadonlyArray<PathAst.QueryParam>) {
|
|
630
|
+
for (const param of params) {
|
|
631
|
+
const {
|
|
632
|
+
optionalFields,
|
|
633
|
+
queryParams: _queryParams,
|
|
634
|
+
requiredFields,
|
|
635
|
+
} = getSchemaFields([param.value]);
|
|
636
|
+
queryParams.push([param.name, { optionalFields, requiredFields }]);
|
|
637
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
638
|
+
queryParams.push(..._queryParams);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function addParts(parts: ReadonlyArray<PathAst>) {
|
|
643
|
+
for (const part of parts) {
|
|
644
|
+
if (part.type === "parameter") {
|
|
645
|
+
addParameter(part);
|
|
646
|
+
} else if (part.type === "wildcard") {
|
|
647
|
+
requiredFields.push(["*", Schema.String]);
|
|
648
|
+
} else if (part.type === "query-params") {
|
|
649
|
+
addQueryParams(part.value);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
addParts(parts);
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
requiredFields,
|
|
658
|
+
optionalFields,
|
|
659
|
+
queryParams,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export function getSchemas<const Parts extends ReadonlyArray<PathAst>>(parts: Parts) {
|
|
664
|
+
const { optionalFields, queryParams, requiredFields } = getSchemaFields(parts);
|
|
665
|
+
const pathFields = Object.fromEntries(requiredFields);
|
|
666
|
+
const queryFields = Object.fromEntries(
|
|
667
|
+
queryParams.map(([name, { optionalFields, requiredFields }]) => [
|
|
668
|
+
name,
|
|
669
|
+
Schema.StructWithRest(
|
|
670
|
+
Schema.Struct(Object.fromEntries(requiredFields)),
|
|
671
|
+
optionalFields.map(([key, value]) => Schema.Record(key, value)),
|
|
672
|
+
),
|
|
673
|
+
]),
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
const pathSchema = Schema.StructWithRest(
|
|
677
|
+
Schema.Struct(pathFields),
|
|
678
|
+
optionalFields.map(([key, value]) => Schema.Record(key, value)),
|
|
679
|
+
);
|
|
680
|
+
const querySchema = Schema.Struct(queryFields);
|
|
681
|
+
const paramsSchema = Schema.StructWithRest(
|
|
682
|
+
Schema.Struct({ ...pathFields, ...queryFields }),
|
|
683
|
+
optionalFields.map(([key, value]) => Schema.Record(key, value)),
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
pathSchema,
|
|
688
|
+
querySchema,
|
|
689
|
+
paramsSchema,
|
|
690
|
+
} as const;
|
|
691
|
+
}
|