foldkit 0.109.0 → 0.110.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.
@@ -1,4 +1,4 @@
1
- import { Effect, Record, Schema } from 'effect';
1
+ import { Array, Effect, Record, Schema } from 'effect';
2
2
  import { Url } from '../url/index.js';
3
3
  declare const ParseError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
4
4
  readonly _tag: "ParseError";
@@ -50,12 +50,21 @@ export type Router<A> = BuildFn<A> & {
50
50
  build: BuildFn<A>;
51
51
  };
52
52
  /**
53
- * A `Biparser` that has been terminated (e.g. by `query`) and cannot
54
- * be extended with `slash`.
53
+ * A `Biparser` that has been terminated (e.g. by `query` or `rest`)
54
+ * and cannot be extended with `slash`.
55
55
  */
56
56
  export type TerminalParser<A> = Biparser<A> & {
57
57
  readonly __terminal: true;
58
58
  };
59
+ /**
60
+ * A `Biparser` that can still be extended with `slash`. Terminal parsers
61
+ * (`query`, `rest`) do not qualify, since nothing can follow them in
62
+ * the path.
63
+ */
64
+ export type ExtendableBiparser<A> = Biparser<A> & {
65
+ readonly __terminal?: never;
66
+ readonly 'Cannot use slash after a terminal parser - nothing can follow query or rest'?: never;
67
+ };
59
68
  /**
60
69
  * Creates a parser that matches an exact URL path segment.
61
70
  *
@@ -99,6 +108,28 @@ export declare const int: <K extends string>(name: K) => Biparser<Record<K, numb
99
108
  * Succeeds only when the URL path is exactly `/`.
100
109
  */
101
110
  export declare const root: Biparser<{}>;
111
+ /**
112
+ * Creates a parser that captures all remaining URL segments as a named
113
+ * non-empty array field.
114
+ *
115
+ * Requires at least one remaining segment. A bare prefix like `/files`
116
+ * does not match; give it its own route alongside the rest route.
117
+ *
118
+ * A rest route also matches every URL that a more specific route under
119
+ * the same prefix accepts, so in `oneOf` the specific route must come
120
+ * first.
121
+ *
122
+ * Nothing can follow `rest` in the path, so the result is a
123
+ * `TerminalParser`. It can still be extended with `query`.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * pipe(literal('files'), slash(rest('path')))
128
+ * // parses /files/documents/taxes/2024.pdf
129
+ * // into { path: ['documents', 'taxes', '2024.pdf'] }
130
+ * ```
131
+ */
132
+ export declare const rest: <K extends string>(name: K) => TerminalParser<Record<K, Array.NonEmptyReadonlyArray<string>>>;
102
133
  /**
103
134
  * A parse-only parser with no print/build capabilities.
104
135
  *
@@ -111,7 +142,12 @@ export type Parser<A> = {
111
142
  type ParserInput = Biparser<any> | Parser<any>;
112
143
  type InferParsed<P> = P extends Biparser<infer A> ? A : P extends Parser<infer A> ? A : never;
113
144
  /**
114
- * Combines multiple parsers, trying each in order until one succeeds.
145
+ * Combines multiple parsers, trying each in order until one matches the
146
+ * entire path.
147
+ *
148
+ * A parser only matches when it consumes every segment, so a route never
149
+ * shadows a longer route that shares its prefix. When several parsers
150
+ * fully match the same URL, the first one wins.
115
151
  *
116
152
  * Returns a `Parser` (parse-only) since the union of different route
117
153
  * shapes cannot provide a single unified print function.
@@ -140,17 +176,19 @@ export declare const mapTo: {
140
176
  /**
141
177
  * Composes two `Biparser`s sequentially, combining their parsed values.
142
178
  *
143
- * Cannot be used after `query` since query parameters are terminal.
179
+ * Cannot be used after a terminal parser (`query` or `rest`).
180
+ * Composing with a terminal second parser yields a `TerminalParser`,
181
+ * so terminality survives the composition.
144
182
  *
145
183
  * @example
146
184
  * ```ts
147
185
  * pipe(literal('users'), slash(int('id'))) // matches /users/42
148
186
  * ```
149
187
  */
150
- export declare const slash: <A extends Record<string, unknown>, B extends Record<string, unknown>>(parserB: Biparser<A>) => (parserA: Biparser<B> & {
151
- readonly __terminal?: never;
152
- readonly "Cannot use slash after query - query parameters must be terminal"?: never;
153
- }) => Biparser<B & A>;
188
+ export declare const slash: {
189
+ <A extends Record<string, unknown>, B extends Record<string, unknown>>(parserB: TerminalParser<A>): (parserA: ExtendableBiparser<B>) => TerminalParser<B & A>;
190
+ <A extends Record<string, unknown>, B extends Record<string, unknown>>(parserB: Biparser<A>): (parserA: ExtendableBiparser<B>) => Biparser<B & A>;
191
+ };
154
192
  /**
155
193
  * Adds query parameter parsing to a `Biparser` using an Effect `Schema`.
156
194
  *
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/route/parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/route/parser.ts"],"names":[],"mappings":"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"}
@@ -111,7 +111,65 @@ export const root = {
111
111
  print: (_, state) => Effect.succeed(state),
112
112
  };
113
113
  /**
114
- * Combines multiple parsers, trying each in order until one succeeds.
114
+ * Creates a parser that captures all remaining URL segments as a named
115
+ * non-empty array field.
116
+ *
117
+ * Requires at least one remaining segment. A bare prefix like `/files`
118
+ * does not match; give it its own route alongside the rest route.
119
+ *
120
+ * A rest route also matches every URL that a more specific route under
121
+ * the same prefix accepts, so in `oneOf` the specific route must come
122
+ * first.
123
+ *
124
+ * Nothing can follow `rest` in the path, so the result is a
125
+ * `TerminalParser`. It can still be extended with `query`.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * pipe(literal('files'), slash(rest('path')))
130
+ * // parses /files/documents/taxes/2024.pdf
131
+ * // into { path: ['documents', 'taxes', '2024.pdf'] }
132
+ * ```
133
+ */
134
+ export const rest = (name) => {
135
+ const parser = {
136
+ parse: segments => Array.match(segments, {
137
+ onEmpty: () => Effect.fail(new ParseError({
138
+ message: `Expected remaining segments (${name})`,
139
+ expected: `remaining segments (${name})`,
140
+ actual: 'end of path',
141
+ position: 0,
142
+ })),
143
+ onNonEmpty: remainingSegments => Effect.succeed([
144
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
145
+ { [name]: remainingSegments },
146
+ [],
147
+ ]),
148
+ }),
149
+ print: (value, state) => Effect.succeed({
150
+ ...state,
151
+ segments: [...state.segments, ...value[name]],
152
+ }),
153
+ };
154
+ return makeTerminalParser(parser);
155
+ };
156
+ const complete = ([value, remaining]) => Array.match(remaining, {
157
+ onEmpty: () => Effect.succeed([value, remaining]),
158
+ onNonEmpty: () => {
159
+ const remainingSegments = Array.join(remaining, '/');
160
+ return Effect.fail(new ParseError({
161
+ message: `Unexpected remaining segments: ${remainingSegments}`,
162
+ actual: remainingSegments,
163
+ }));
164
+ },
165
+ });
166
+ /**
167
+ * Combines multiple parsers, trying each in order until one matches the
168
+ * entire path.
169
+ *
170
+ * A parser only matches when it consumes every segment, so a route never
171
+ * shadows a longer route that shares its prefix. When several parsers
172
+ * fully match the same URL, the first one wins.
115
173
  *
116
174
  * Returns a `Parser` (parse-only) since the union of different route
117
175
  * shapes cannot provide a single unified print function.
@@ -121,7 +179,7 @@ export const oneOf = (...parsers) => ({
121
179
  onEmpty: () => Effect.fail(new ParseError({
122
180
  message: `No parsers provided for path: /${Array.join(segments, '/')}`,
123
181
  })),
124
- onNonEmpty: () => Effect.firstSuccessOf(Array.map(parsers, parser => parser.parse(segments, search))),
182
+ onNonEmpty: () => Effect.firstSuccessOf(Array.map(parsers, parser => pipe(parser.parse(segments, search), Effect.flatMap(complete)))),
125
183
  }),
126
184
  });
127
185
  /**
@@ -154,7 +212,9 @@ export const mapTo = (appRouteConstructor) => {
154
212
  /**
155
213
  * Composes two `Biparser`s sequentially, combining their parsed values.
156
214
  *
157
- * Cannot be used after `query` since query parameters are terminal.
215
+ * Cannot be used after a terminal parser (`query` or `rest`).
216
+ * Composing with a terminal second parser yields a `TerminalParser`,
217
+ * so terminality survives the composition.
158
218
  *
159
219
  * @example
160
220
  * ```ts
@@ -216,17 +276,7 @@ export const query = (schema) => (parser) => {
216
276
  return makeTerminalParser(queryParser);
217
277
  };
218
278
  const pathToSegments = flow(String.split('/'), Array.filter(String.isNonEmpty));
219
- const complete = ([value, remaining]) => Array.match(remaining, {
220
- onEmpty: () => Effect.succeed(value),
221
- onNonEmpty: () => {
222
- const remainingSegments = Array.join(remaining, '/');
223
- return Effect.fail(new ParseError({
224
- message: `Unexpected remaining segments: ${remainingSegments}`,
225
- actual: remainingSegments,
226
- }));
227
- },
228
- });
229
- const parseUrl = (parser) => (url) => pipe(pathToSegments(url.pathname), segments => parser.parse(segments, Option.getOrUndefined(url.search)), Effect.flatMap(complete));
279
+ const parseUrl = (parser) => (url) => pipe(pathToSegments(url.pathname), segments => parser.parse(segments, Option.getOrUndefined(url.search)), Effect.flatMap(complete), Effect.map(([value]) => value));
230
280
  /**
231
281
  * Parses a URL against a parser, falling back to a not-found route if no
232
282
  * parser matches.
@@ -1,3 +1,3 @@
1
- export { ParseError, literal, param, string, int, root, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
2
- export type { ParseResult, Biparser, Router, TerminalParser, Parser, } from './index.js';
1
+ export { ParseError, literal, param, string, int, root, rest, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
2
+ export type { ParseResult, Biparser, Router, TerminalParser, ExtendableBiparser, Parser, } from './index.js';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/route/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,OAAO,EACP,KAAK,EACL,MAAM,EACN,GAAG,EACH,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,KAAK,EACL,oBAAoB,EACpB,CAAC,GACF,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,cAAc,EACd,MAAM,GACP,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/route/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,OAAO,EACP,KAAK,EACL,MAAM,EACN,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,EACL,KAAK,EACL,oBAAoB,EACpB,CAAC,GACF,MAAM,YAAY,CAAA;AAEnB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,MAAM,EACN,cAAc,EACd,kBAAkB,EAClB,MAAM,GACP,MAAM,YAAY,CAAA"}
@@ -1 +1 @@
1
- export { ParseError, literal, param, string, int, root, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
1
+ export { ParseError, literal, param, string, int, root, rest, oneOf, mapTo, slash, query, parseUrlWithFallback, r, } from './index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.109.0",
3
+ "version": "0.110.0",
4
4
  "description": "A TypeScript frontend framework, built on Effect and architected like Elm",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",