paramour 0.8.0 → 0.9.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/dist/codec.d.ts +23 -10
- package/dist/codec.js +2 -0
- package/dist/describe.d.ts +4 -2
- package/dist/describe.js +3 -1
- package/dist/errors.d.ts +14 -7
- package/dist/errors.js +25 -10
- package/dist/href.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +9 -8
- package/dist/internal.js +9 -8
- package/dist/p.js +41 -16
- package/dist/path.js +1 -1
- package/dist/route.d.ts +31 -23
- package/dist/route.js +8 -6
- package/dist/safe-decode.d.ts +8 -4
- package/dist/safe-decode.js +6 -11
- package/dist/search.d.ts +54 -49
- package/dist/search.js +38 -9
- package/dist/standard-schema.d.ts +3 -4
- package/package.json +1 -1
package/dist/codec.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Any codec, optionally narrowed to one output type: `AnyCodec<number>` is
|
|
3
|
+
* the supported spelling of "a codec producing numbers" in any presence,
|
|
4
|
+
* catch, or arity state — only `Codec`'s first type parameter is public API;
|
|
5
|
+
* the type-state parameters after it may change in minor releases. The
|
|
6
|
+
* `any` default is deliberate: `~out` appears in inferred method parameter
|
|
7
|
+
* positions (`.default(value: Out)`), which are contravariant under
|
|
8
|
+
* strictFunctionTypes; the `unknown` form would reject every concrete codec.
|
|
5
9
|
*/
|
|
6
|
-
export type AnyCodec = Codec<
|
|
10
|
+
export type AnyCodec<Out = any> = Codec<Out, Presence, boolean, Arity>;
|
|
7
11
|
/** "single" = one wire value per key; "many" = repeated keys (arrays). */
|
|
8
12
|
export type Arity = "many" | "single";
|
|
9
13
|
/**
|
|
@@ -71,18 +75,26 @@ export interface Codec<Out, P extends Presence = "required", C extends boolean =
|
|
|
71
75
|
/** Members of a `p.enum` codec; undefined for every other kind. */
|
|
72
76
|
readonly "~enumMembers": readonly string[] | undefined;
|
|
73
77
|
/**
|
|
74
|
-
* Which builder produced the codec
|
|
75
|
-
*
|
|
76
|
-
* never
|
|
78
|
+
* Which builder produced the codec. Reflection metadata for describeCodec
|
|
79
|
+
* — never consulted by parse/serialize. `p.custom` is always `"custom"`,
|
|
80
|
+
* so a custom codec can never pass itself off as a built-in; its
|
|
81
|
+
* user-chosen name lives in `~label`.
|
|
77
82
|
*/
|
|
78
|
-
readonly "~kind":
|
|
83
|
+
readonly "~kind": CodecKind;
|
|
84
|
+
/** `p.custom`'s display label; undefined for every other codec. */
|
|
85
|
+
readonly "~label": string | undefined;
|
|
79
86
|
/** phantom — carries `Out` for inference; never set at runtime */
|
|
80
87
|
readonly "~out": Out;
|
|
81
88
|
readonly "~parseElement": (raw: string) => unknown;
|
|
82
89
|
readonly "~presence": P;
|
|
83
90
|
readonly "~serializeElement": (value: unknown) => string;
|
|
84
91
|
}
|
|
85
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Which builder produced a codec. New builders add members in minor
|
|
94
|
+
* releases, so exhaustive switches over it need a default branch.
|
|
95
|
+
*/
|
|
96
|
+
export type CodecKind = "array" | "boolean" | "csv" | "custom" | "enum" | "index" | "integer" | "isoDate" | "json" | "number" | "string" | "timestamp";
|
|
97
|
+
export type InferCodecOutput<C extends AnyCodec> = C["~out"];
|
|
86
98
|
/** Codecs legal in a `params:` config — no presence modifiers (D5). */
|
|
87
99
|
export type ParamCodec = Codec<any, "required", boolean>;
|
|
88
100
|
/**
|
|
@@ -108,7 +120,8 @@ export declare function createCodec<Out, A extends Arity = "single">(impl: {
|
|
|
108
120
|
arity?: A;
|
|
109
121
|
element?: AnyCodec;
|
|
110
122
|
enumMembers?: readonly string[];
|
|
111
|
-
kind?:
|
|
123
|
+
kind?: CodecKind;
|
|
124
|
+
label?: string;
|
|
112
125
|
parseElement: (raw: string) => unknown;
|
|
113
126
|
serializeElement: (value: unknown) => string;
|
|
114
127
|
}): Codec<Out, "required", false, A>;
|
package/dist/codec.js
CHANGED
|
@@ -9,6 +9,7 @@ export function createCodec(impl) {
|
|
|
9
9
|
element: impl.element,
|
|
10
10
|
enumMembers: impl.enumMembers,
|
|
11
11
|
kind: impl.kind ?? "custom",
|
|
12
|
+
label: impl.label,
|
|
12
13
|
parseElement: impl.parseElement,
|
|
13
14
|
presence: "required",
|
|
14
15
|
serializeElement: impl.serializeElement,
|
|
@@ -64,6 +65,7 @@ function build(state) {
|
|
|
64
65
|
"~element": state.element,
|
|
65
66
|
"~enumMembers": state.enumMembers,
|
|
66
67
|
"~kind": state.kind,
|
|
68
|
+
"~label": state.label,
|
|
67
69
|
"~parseElement": state.parseElement,
|
|
68
70
|
"~presence": state.presence,
|
|
69
71
|
"~serializeElement": state.serializeElement,
|
package/dist/describe.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCodec, Arity, Presence } from "./codec.js";
|
|
1
|
+
import type { AnyCodec, Arity, CodecKind, Presence } from "./codec.js";
|
|
2
2
|
import type { AnyRoute, RouterKind } from "./route.js";
|
|
3
3
|
/**
|
|
4
4
|
* `.default()` in reflected form. Value-form defaults carry their wire
|
|
@@ -28,7 +28,9 @@ export interface CodecDescription {
|
|
|
28
28
|
*/
|
|
29
29
|
readonly element?: CodecDescription;
|
|
30
30
|
readonly enumMembers?: readonly string[];
|
|
31
|
-
readonly kind:
|
|
31
|
+
readonly kind: CodecKind;
|
|
32
|
+
/** A `p.custom` codec's display label, when it was given one. */
|
|
33
|
+
readonly label?: string;
|
|
32
34
|
readonly presence: Presence;
|
|
33
35
|
}
|
|
34
36
|
/** Rendering styles accepted by {@link formatCodecDescription}. */
|
package/dist/describe.js
CHANGED
|
@@ -28,6 +28,7 @@ export function describeCodec(codec) {
|
|
|
28
28
|
...(element === undefined ? {} : { element: describeCodec(element) }),
|
|
29
29
|
...(enumMembers === undefined ? {} : { enumMembers }),
|
|
30
30
|
kind: codec["~kind"],
|
|
31
|
+
...(codec["~label"] === undefined ? {} : { label: codec["~label"] }),
|
|
31
32
|
presence: codec["~presence"],
|
|
32
33
|
};
|
|
33
34
|
}
|
|
@@ -76,8 +77,9 @@ export function describeRoute(route) {
|
|
|
76
77
|
*/
|
|
77
78
|
export function formatCodecDescription(description, style) {
|
|
78
79
|
const memberSeparator = style === "verbose" ? ", " : "|";
|
|
80
|
+
// A custom codec renders its label when it has one; otherwise its kind.
|
|
79
81
|
const kindLabel = (part) => part.enumMembers === undefined
|
|
80
|
-
? part.kind
|
|
82
|
+
? (part.label ?? part.kind)
|
|
81
83
|
: `enum(${part.enumMembers.join(memberSeparator)})`;
|
|
82
84
|
// Composite labels: a one-key list wraps its element (`csv<integer>`); a
|
|
83
85
|
// repeated-key list IS its element, pluralized (`integer[]`) — the
|
package/dist/errors.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export declare class ParamourError extends Error {
|
|
|
57
57
|
/** Aggregate failure for a whole route-params decode. */
|
|
58
58
|
export declare class ParamsDecodeError extends ParamourError {
|
|
59
59
|
readonly issues: readonly Issue[];
|
|
60
|
+
readonly name: "ParamsDecodeError";
|
|
60
61
|
/** The failed route's path pattern; null when decoded outside a route. */
|
|
61
62
|
readonly route: null | string;
|
|
62
63
|
constructor(issues: readonly Issue[], route?: null | string);
|
|
@@ -68,7 +69,14 @@ export declare class ParamsDecodeError extends ParamourError {
|
|
|
68
69
|
*/
|
|
69
70
|
export declare class ParseError extends ParamourError {
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
+
* Literal per class, so the decode errors stay structurally distinct
|
|
73
|
+
* (the `SafeResult` error parameter relies on it) and `error.name`
|
|
74
|
+
* survives minification.
|
|
75
|
+
*/
|
|
76
|
+
readonly name: "ParseError";
|
|
77
|
+
/**
|
|
78
|
+
* Internal, outside semver: true when the message follows core's
|
|
79
|
+
* grammar-authoring convention —
|
|
72
80
|
* it quotes the offending wire value and names the grammar it failed
|
|
73
81
|
* (`'"x" is not an integer'`). Only core's own grammar throw sites set
|
|
74
82
|
* it (via {@link grammarParseError}); schema-validation failures and
|
|
@@ -77,16 +85,13 @@ export declare class ParseError extends ParamourError {
|
|
|
77
85
|
* expected-shape context themselves. This flag — never message sniffing
|
|
78
86
|
* — is what issue producers key `reason: "parse" | "validate"` on.
|
|
79
87
|
*/
|
|
80
|
-
readonly selfDescribing: boolean;
|
|
81
|
-
constructor(message: string, options?: {
|
|
82
|
-
cause?: unknown;
|
|
83
|
-
selfDescribing?: boolean;
|
|
84
|
-
});
|
|
88
|
+
readonly "~selfDescribing": boolean;
|
|
85
89
|
static [Symbol.hasInstance](value: unknown): value is ParseError;
|
|
86
90
|
}
|
|
87
91
|
/** Aggregate failure for a whole search-params decode. */
|
|
88
92
|
export declare class SearchDecodeError extends ParamourError {
|
|
89
93
|
readonly issues: readonly Issue[];
|
|
94
|
+
readonly name: "SearchDecodeError";
|
|
90
95
|
/** The failed route's path pattern; null when decoded outside a route. */
|
|
91
96
|
readonly route: null | string;
|
|
92
97
|
constructor(issues: readonly Issue[], route?: null | string);
|
|
@@ -102,11 +107,13 @@ export declare class SearchDecodeError extends ParamourError {
|
|
|
102
107
|
export declare class SearchSourceError extends ParamourError {
|
|
103
108
|
/** The offending source key, or null when the source itself is malformed. */
|
|
104
109
|
readonly key: null | string;
|
|
110
|
+
readonly name: "SearchSourceError";
|
|
105
111
|
constructor(message: string, key: null | string);
|
|
106
112
|
static [Symbol.hasInstance](value: unknown): value is SearchSourceError;
|
|
107
113
|
}
|
|
108
114
|
/** A value could not be serialized to the wire (bad type, non-finite, etc.). */
|
|
109
115
|
export declare class SerializeError extends ParamourError {
|
|
116
|
+
readonly name: "SerializeError";
|
|
110
117
|
static [Symbol.hasInstance](value: unknown): value is SerializeError;
|
|
111
118
|
}
|
|
112
119
|
/**
|
|
@@ -137,7 +144,7 @@ export declare function grammarParseError(message: string): ParseError;
|
|
|
137
144
|
* Maps a caught {@link ParseError} to its {@link Issue} reason: core's
|
|
138
145
|
* grammar-authored messages are `"parse"`, everything else — schema
|
|
139
146
|
* validators, rebranded custom-codec throws — is `"validate"`. Keyed on the
|
|
140
|
-
* structural
|
|
147
|
+
* structural `~selfDescribing` flag, never on message sniffing; shared by
|
|
141
148
|
* search.ts and path.ts so both surfaces classify identically. Not exported
|
|
142
149
|
* from the package.
|
|
143
150
|
*/
|
package/dist/errors.js
CHANGED
|
@@ -20,7 +20,12 @@ export class ParamourError extends Error {
|
|
|
20
20
|
}
|
|
21
21
|
constructor(message, options) {
|
|
22
22
|
super(message, options);
|
|
23
|
-
|
|
23
|
+
// Paramour's own classes pin a literal `name` field (which runs after
|
|
24
|
+
// this and wins) — a minifier that drops class names would otherwise
|
|
25
|
+
// rename them in production. User subclasses fall back to their class
|
|
26
|
+
// name.
|
|
27
|
+
this.name =
|
|
28
|
+
new.target === ParamourError ? "ParamourError" : new.target.name;
|
|
24
29
|
}
|
|
25
30
|
// Each class checks its OWN brand: an inherited base check would make
|
|
26
31
|
// every ParamourError pass `instanceof ParseError`. The type-predicate
|
|
@@ -35,6 +40,7 @@ export class ParamsDecodeError extends ParamourError {
|
|
|
35
40
|
brandPrototype(this, paramsDecodeErrorBrand);
|
|
36
41
|
}
|
|
37
42
|
issues;
|
|
43
|
+
name = "ParamsDecodeError";
|
|
38
44
|
/** The failed route's path pattern; null when decoded outside a route. */
|
|
39
45
|
route;
|
|
40
46
|
constructor(issues, route = null) {
|
|
@@ -55,7 +61,14 @@ export class ParseError extends ParamourError {
|
|
|
55
61
|
brandPrototype(this, parseErrorBrand);
|
|
56
62
|
}
|
|
57
63
|
/**
|
|
58
|
-
*
|
|
64
|
+
* Literal per class, so the decode errors stay structurally distinct
|
|
65
|
+
* (the `SafeResult` error parameter relies on it) and `error.name`
|
|
66
|
+
* survives minification.
|
|
67
|
+
*/
|
|
68
|
+
name = "ParseError";
|
|
69
|
+
/**
|
|
70
|
+
* Internal, outside semver: true when the message follows core's
|
|
71
|
+
* grammar-authoring convention —
|
|
59
72
|
* it quotes the offending wire value and names the grammar it failed
|
|
60
73
|
* (`'"x" is not an integer'`). Only core's own grammar throw sites set
|
|
61
74
|
* it (via {@link grammarParseError}); schema-validation failures and
|
|
@@ -64,11 +77,7 @@ export class ParseError extends ParamourError {
|
|
|
64
77
|
* expected-shape context themselves. This flag — never message sniffing
|
|
65
78
|
* — is what issue producers key `reason: "parse" | "validate"` on.
|
|
66
79
|
*/
|
|
67
|
-
selfDescribing;
|
|
68
|
-
constructor(message, options) {
|
|
69
|
-
super(message, options);
|
|
70
|
-
this.selfDescribing = options?.selfDescribing ?? false;
|
|
71
|
-
}
|
|
80
|
+
"~selfDescribing" = false;
|
|
72
81
|
static [Symbol.hasInstance](value) {
|
|
73
82
|
return hasBrand(value, parseErrorBrand);
|
|
74
83
|
}
|
|
@@ -79,6 +88,7 @@ export class SearchDecodeError extends ParamourError {
|
|
|
79
88
|
brandPrototype(this, searchDecodeErrorBrand);
|
|
80
89
|
}
|
|
81
90
|
issues;
|
|
91
|
+
name = "SearchDecodeError";
|
|
82
92
|
/** The failed route's path pattern; null when decoded outside a route. */
|
|
83
93
|
route;
|
|
84
94
|
constructor(issues, route = null) {
|
|
@@ -103,6 +113,7 @@ export class SearchSourceError extends ParamourError {
|
|
|
103
113
|
}
|
|
104
114
|
/** The offending source key, or null when the source itself is malformed. */
|
|
105
115
|
key;
|
|
116
|
+
name = "SearchSourceError";
|
|
106
117
|
constructor(message, key) {
|
|
107
118
|
super(message);
|
|
108
119
|
this.key = key;
|
|
@@ -116,6 +127,7 @@ export class SerializeError extends ParamourError {
|
|
|
116
127
|
static {
|
|
117
128
|
brandPrototype(this, serializeErrorBrand);
|
|
118
129
|
}
|
|
130
|
+
name = "SerializeError";
|
|
119
131
|
static [Symbol.hasInstance](value) {
|
|
120
132
|
return hasBrand(value, serializeErrorBrand);
|
|
121
133
|
}
|
|
@@ -148,18 +160,21 @@ export function foreignMessage(error) {
|
|
|
148
160
|
* Not exported from the package.
|
|
149
161
|
*/
|
|
150
162
|
export function grammarParseError(message) {
|
|
151
|
-
|
|
163
|
+
const error = new ParseError(message);
|
|
164
|
+
// The one setter: the flag is readonly to everyone else.
|
|
165
|
+
error["~selfDescribing"] = true;
|
|
166
|
+
return error;
|
|
152
167
|
}
|
|
153
168
|
/**
|
|
154
169
|
* Maps a caught {@link ParseError} to its {@link Issue} reason: core's
|
|
155
170
|
* grammar-authored messages are `"parse"`, everything else — schema
|
|
156
171
|
* validators, rebranded custom-codec throws — is `"validate"`. Keyed on the
|
|
157
|
-
* structural
|
|
172
|
+
* structural `~selfDescribing` flag, never on message sniffing; shared by
|
|
158
173
|
* search.ts and path.ts so both surfaces classify identically. Not exported
|
|
159
174
|
* from the package.
|
|
160
175
|
*/
|
|
161
176
|
export function parseIssueReason(error) {
|
|
162
|
-
return error
|
|
177
|
+
return error["~selfDescribing"] ? "parse" : "validate";
|
|
163
178
|
}
|
|
164
179
|
/**
|
|
165
180
|
* Runs user (or platform) code, letting paramour's own errors pass through
|
package/dist/href.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AnyRoute, RegisteredStaticRoutePaths } from "./route.js";
|
|
2
2
|
import { type InferParamsInput } from "./path.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { type InferSearchInput } from "./search.js";
|
|
4
4
|
/**
|
|
5
5
|
* Type-only brand carrier: no runtime value ever exists — the brand is
|
|
6
6
|
* applied by a compile-time cast, so Href costs nothing at runtime.
|
|
@@ -30,7 +30,7 @@ export type HrefArgs<R extends AnyRoute> = Record<never, never> extends InferHre
|
|
|
30
30
|
* keys AT ALL may not be passed even empty (see PartFor). `hash` implements
|
|
31
31
|
* S10 — fragments come only from an explicit caller option.
|
|
32
32
|
*/
|
|
33
|
-
export type InferHrefInput<R extends AnyRoute> = PartFor<"params", InferParamsInput<R>> & PartFor<"search",
|
|
33
|
+
export type InferHrefInput<R extends AnyRoute> = PartFor<"params", InferParamsInput<R>> & PartFor<"search", InferSearchInput<R["~search"]>> & {
|
|
34
34
|
hash?: string;
|
|
35
35
|
};
|
|
36
36
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { type AnyCodec, type Arity, type Codec, type
|
|
1
|
+
export { type AnyCodec, type Arity, type Codec, type CodecKind, type InferCodecOutput, type ParamCodec, type Presence, type PresenceOf, } from "./codec.js";
|
|
2
2
|
export { type CodecDefaultDescription, type CodecDescription, type CodecFormatStyle, describeCodec, describeRoute, formatCodecDescription, type ParamDescription, type RouteDescription, type SearchDescription, } from "./describe.js";
|
|
3
3
|
export { type Issue, type IssueReason, ParamourError, ParamsDecodeError, ParseError, type RouteDecodeError, SearchDecodeError, SearchSourceError, SerializeError, } from "./errors.js";
|
|
4
4
|
export { href, type Href, type HrefArgs, type InferHrefInput, type StaticHrefOptions, } from "./href.js";
|
|
5
5
|
export { p } from "./p.js";
|
|
6
6
|
export { buildPath, decodeParams, type DecodeParamsOptions, encodeParams, encodeStaticParams, type InferStaticParams, type ParamsSource, } from "./path.js";
|
|
7
|
-
export { type AnyAppRoute, type AnyPagesRoute, type AnyRoute, type AppRoute, defineAppRoute, definePagesRoute, type InferRouteParams, type PagesContext, type PagesRoute, type ParamourRegister, type ParamsConfig, type ParamsProps, type
|
|
7
|
+
export { type AnyAppRoute, type AnyPagesRoute, type AnyRoute, type AppRoute, defineAppRoute, definePagesRoute, type InferRouteParams, type InferRouteSearch, type PagesContext, type PagesRoute, type ParamourRegister, type ParamsConfig, type ParamsProps, type ParamsPropsLike, type RegisteredAppRoutePaths, type RegisteredPagesRoutePaths, type RegisteredStaticAppRoutePaths, type RegisteredStaticPagesRoutePaths, type RegisteredStaticRoutePaths, type Route, type RouteConfig, type RouteProps, type RoutePropsLike, type RouterKind, type SafeResult, type SearchProps, type SearchPropsLike, } from "./route.js";
|
|
8
8
|
export { safeDecodeParams, safeDecodeSearch } from "./safe-decode.js";
|
|
9
|
-
export { buildSearchString, decodeSearch, encodeSearch, type InferSearchInput, type InferSearchOutput, isRawSearch, rawSearch, type RawSearch, type SearchConfig, type
|
|
9
|
+
export { buildSearchString, decodeSearch, encodeSearch, type InferSearchInput, type InferSearchOutput, isRawSearch, parseValue, rawSearch, type RawSearch, type SearchConfig, type SearchSlot, type SearchSource, searchToString, serializeValue, } from "./search.js";
|
|
10
10
|
export { standardSearchSchema, type StandardSearchSchema, } from "./standard-schema.js";
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,5 @@ export { p } from "./p.js";
|
|
|
6
6
|
export { buildPath, decodeParams, encodeParams, encodeStaticParams, } from "./path.js";
|
|
7
7
|
export { defineAppRoute, definePagesRoute, } from "./route.js";
|
|
8
8
|
export { safeDecodeParams, safeDecodeSearch } from "./safe-decode.js";
|
|
9
|
-
export { buildSearchString, decodeSearch, encodeSearch, isRawSearch, rawSearch, searchToString, serializeValue, } from "./search.js";
|
|
9
|
+
export { buildSearchString, decodeSearch, encodeSearch, isRawSearch, parseValue, rawSearch, searchToString, serializeValue, } from "./search.js";
|
|
10
10
|
export { standardSearchSchema, } from "./standard-schema.js";
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `paramour/internal` entry:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* The `paramour/internal` entry: helpers for derived tooling (devtools,
|
|
3
|
+
* adapters), NOT for app authors — they live off the main barrel so the
|
|
4
|
+
* docs' Reference section stays the app-author surface. Covered by semver
|
|
5
|
+
* within a major all the same (additions only until the next major): the
|
|
6
|
+
* devtools panel peers on `paramour` with a caret range, so a newer core
|
|
7
|
+
* must never break an installed panel that imports from here. These exist
|
|
8
|
+
* so reflection-driven consumers (the panel's synthesized-issue labels and
|
|
9
|
+
* foreign-error rendering) share core's implementation instead of
|
|
10
|
+
* re-deriving it.
|
|
9
11
|
*/
|
|
10
12
|
export { codecShapeLabel } from "./describe.js";
|
|
11
13
|
export { foreignMessage } from "./errors.js";
|
|
12
|
-
export { parseValue } from "./search.js";
|
package/dist/internal.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `paramour/internal` entry:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* The `paramour/internal` entry: helpers for derived tooling (devtools,
|
|
3
|
+
* adapters), NOT for app authors — they live off the main barrel so the
|
|
4
|
+
* docs' Reference section stays the app-author surface. Covered by semver
|
|
5
|
+
* within a major all the same (additions only until the next major): the
|
|
6
|
+
* devtools panel peers on `paramour` with a caret range, so a newer core
|
|
7
|
+
* must never break an installed panel that imports from here. These exist
|
|
8
|
+
* so reflection-driven consumers (the panel's synthesized-issue labels and
|
|
9
|
+
* foreign-error rendering) share core's implementation instead of
|
|
10
|
+
* re-deriving it.
|
|
9
11
|
*/
|
|
10
12
|
export { codecShapeLabel } from "./describe.js";
|
|
11
13
|
export { foreignMessage } from "./errors.js";
|
|
12
|
-
export { parseValue } from "./search.js";
|
package/dist/p.js
CHANGED
|
@@ -6,9 +6,11 @@ import { runStandardSchemaSync } from "./schema.js";
|
|
|
6
6
|
const INTEGER_RE = /^-?\d+$/;
|
|
7
7
|
const NUMBER_RE = /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;
|
|
8
8
|
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
9
|
-
// Canonical emit is Date#toISOString (milliseconds always); parse
|
|
10
|
-
// missing milliseconds
|
|
11
|
-
|
|
9
|
+
// Canonical emit is Date#toISOString (UTC, milliseconds always); parse
|
|
10
|
+
// tolerates missing milliseconds and accepts a `Z` or `±HH:MM` offset, so
|
|
11
|
+
// links from systems that emit local-offset timestamps decode to the same
|
|
12
|
+
// instant. The emit side never produces an offset: one instant, one URL.
|
|
13
|
+
const TIMESTAMP_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(?:Z|([+-])(\d{2}):(\d{2}))$/;
|
|
12
14
|
/**
|
|
13
15
|
* Serialize-side Date guard. Years outside 0000–9999 are rejected:
|
|
14
16
|
* toISOString switches to the expanded ±6-digit-year form there, which the
|
|
@@ -190,8 +192,8 @@ export const p = {
|
|
|
190
192
|
// CV2: presence, catch, and arity-many inners are excluded by the
|
|
191
193
|
// parameter type (the same type-state philosophy); resolveListElement
|
|
192
194
|
// mirrors that type-state for JS consumers. Nesting is detected
|
|
193
|
-
// structurally via ~element (never via ~kind, which is reflection-only
|
|
194
|
-
//
|
|
195
|
+
// structurally via ~element (never via ~kind, which is reflection-only).
|
|
196
|
+
// Comma-emitting p.custom inners are
|
|
195
197
|
// undetectable here and are caught by the CV4 serialize guard instead.
|
|
196
198
|
const inner = resolveListElement(element, "p.csv");
|
|
197
199
|
// The shared guard ran first: p.array also carries ~element (PP1), and
|
|
@@ -264,7 +266,7 @@ export const p = {
|
|
|
264
266
|
// aggregation. .catch() recovers foreign parse failures only, which
|
|
265
267
|
// rebrandForeign normalizes to ParseError so recovery sees them.
|
|
266
268
|
return createCodec({
|
|
267
|
-
...(codec.label === undefined ? {} : {
|
|
269
|
+
...(codec.label === undefined ? {} : { label: codec.label }),
|
|
268
270
|
parseElement: (raw) => rebrandForeign(() => codec.parse(raw),
|
|
269
271
|
// Not grammarParseError: foreign prose may name neither the value
|
|
270
272
|
// nor the grammar, so decode issues classify it "validate" and
|
|
@@ -409,20 +411,43 @@ export const p = {
|
|
|
409
411
|
return createCodec({
|
|
410
412
|
kind: "timestamp",
|
|
411
413
|
parseElement: (raw) => {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
+
const match = TIMESTAMP_RE.exec(raw);
|
|
415
|
+
if (!match) {
|
|
416
|
+
throw grammarParseError(`"${raw}" is not an ISO 8601 timestamp`);
|
|
414
417
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
+
const [year, month, day, hour, minute, second] = match
|
|
419
|
+
.slice(1, 7)
|
|
420
|
+
.map(Number);
|
|
421
|
+
const millis = Number((match[7] ?? "").padEnd(3, "0"));
|
|
422
|
+
const sign = match[8] === "-" ? -1 : 1;
|
|
423
|
+
const offsetHours = Number(match[9] ?? "0");
|
|
424
|
+
const offsetMinutes = Number(match[10] ?? "0");
|
|
425
|
+
if (offsetHours > 23 || offsetMinutes > 59) {
|
|
426
|
+
throw grammarParseError(`"${raw}" has an impossible UTC offset`);
|
|
418
427
|
}
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
428
|
+
// Built field by field — Date.UTC maps years 0–99 to 1900–1999 —
|
|
429
|
+
// then checked field by field: the engine silently normalizes
|
|
430
|
+
// impossible fields (Feb 30 → Mar 1, 24:00 → next day), which must
|
|
431
|
+
// be rejected, not reinterpreted.
|
|
432
|
+
const local = new Date(0);
|
|
433
|
+
local.setUTCFullYear(year, month - 1, day);
|
|
434
|
+
local.setUTCHours(hour, minute, second, millis);
|
|
435
|
+
if (local.getUTCFullYear() !== year ||
|
|
436
|
+
local.getUTCMonth() !== month - 1 ||
|
|
437
|
+
local.getUTCDate() !== day ||
|
|
438
|
+
local.getUTCHours() !== hour ||
|
|
439
|
+
local.getUTCMinutes() !== minute ||
|
|
440
|
+
local.getUTCSeconds() !== second) {
|
|
424
441
|
throw grammarParseError(`"${raw}" is not a real instant`);
|
|
425
442
|
}
|
|
443
|
+
const date = new Date(local.getTime() - sign * (offsetHours * 60 + offsetMinutes) * 60_000);
|
|
444
|
+
// An offset can push the instant outside what the canonical UTC form
|
|
445
|
+
// can represent (0000-01-01T00:30+01:00 is year -1); every decoded
|
|
446
|
+
// value must re-serialize, so reject it here rather than at encode.
|
|
447
|
+
const utcYear = date.getUTCFullYear();
|
|
448
|
+
if (utcYear < 0 || utcYear > 9999) {
|
|
449
|
+
throw grammarParseError(`"${raw}" is outside the representable 0000-9999 range in UTC`);
|
|
450
|
+
}
|
|
426
451
|
return date;
|
|
427
452
|
},
|
|
428
453
|
serializeElement: (value) => expectSerializableDate(value).toISOString(),
|
package/dist/path.js
CHANGED
|
@@ -98,7 +98,7 @@ export function decodeParams(route, source, options) {
|
|
|
98
98
|
key: segment.name,
|
|
99
99
|
message: error.message,
|
|
100
100
|
// "parse" vs "validate" comes from the ParseError's own
|
|
101
|
-
// selfDescribing flag — structural, never message sniffing.
|
|
101
|
+
// ~selfDescribing flag — structural, never message sniffing.
|
|
102
102
|
reason: parseIssueReason(error),
|
|
103
103
|
// Issue.wire is the codec-grammar-layer value — the DECODED
|
|
104
104
|
// segment, not the raw URL text — matching decodeSearch,
|
package/dist/route.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AnyCodec,
|
|
2
|
-
import { type RouteDecodeError } from "./errors.js";
|
|
1
|
+
import type { AnyCodec, InferCodecOutput, ParamCodec } from "./codec.js";
|
|
2
|
+
import { ParamsDecodeError, type RouteDecodeError, SearchDecodeError } from "./errors.js";
|
|
3
3
|
import { type ParamsSource, type PathSegment } from "./path.js";
|
|
4
|
-
import { type
|
|
4
|
+
import { type InferSearchOutput, type SearchSlot } from "./search.js";
|
|
5
5
|
/**
|
|
6
6
|
* `any` is deliberate (same variance gotcha as AnyCodec): codec configs
|
|
7
7
|
* reach contravariant positions through the parse methods and `HrefArgs`;
|
|
@@ -27,20 +27,20 @@ export interface AppRoute<Path extends string, PC extends ParamsConfig<Path>, SC
|
|
|
27
27
|
* FIRST — a params grammar failure means the URL doesn't denote this
|
|
28
28
|
* route at all (morally a 404), so it throws before search is decoded.
|
|
29
29
|
*/
|
|
30
|
-
parse(props:
|
|
30
|
+
parse(props: RoutePropsLike): Promise<{
|
|
31
31
|
params: ParamsOutput<Path, PC>;
|
|
32
|
-
search:
|
|
32
|
+
search: InferSearchOutput<SC>;
|
|
33
33
|
}>;
|
|
34
34
|
/** Bare params object — layout props are structurally assignable. */
|
|
35
|
-
parseParams(props:
|
|
35
|
+
parseParams(props: ParamsPropsLike): Promise<ParamsOutput<Path, PC>>;
|
|
36
36
|
/** Bare search object — the search half alone. */
|
|
37
|
-
parseSearch(props:
|
|
38
|
-
safeParse(props:
|
|
37
|
+
parseSearch(props: SearchPropsLike): Promise<InferSearchOutput<SC>>;
|
|
38
|
+
safeParse(props: RoutePropsLike): Promise<SafeResult<{
|
|
39
39
|
params: ParamsOutput<Path, PC>;
|
|
40
|
-
search:
|
|
40
|
+
search: InferSearchOutput<SC>;
|
|
41
41
|
}>>;
|
|
42
|
-
safeParseParams(props:
|
|
43
|
-
safeParseSearch(props:
|
|
42
|
+
safeParseParams(props: ParamsPropsLike): Promise<SafeResult<ParamsOutput<Path, PC>, ParamsDecodeError>>;
|
|
43
|
+
safeParseSearch(props: SearchPropsLike): Promise<SafeResult<InferSearchOutput<SC>, SearchDecodeError>>;
|
|
44
44
|
}
|
|
45
45
|
/** Names of `[...name]` catch-all segments in the path literal. */
|
|
46
46
|
export type CatchAllNames<Path extends string> = Segments<Path> extends infer S extends string ? S extends `[...${infer Name}]` ? NonEmptyName<Name> : never : never;
|
|
@@ -52,9 +52,14 @@ export type CatchAllNames<Path extends string> = Segments<Path> extends infer S
|
|
|
52
52
|
export type ConformParams<Path extends string, PC> = PC & Record<Exclude<keyof PC, PathParamNames<Path>>, never>;
|
|
53
53
|
/** Decoded params object type for a route; see {@link ParamsOutput}. */
|
|
54
54
|
export type InferRouteParams<R extends AnyRoute> = ParamsOutput<R["path"], R["~params"]>;
|
|
55
|
+
/**
|
|
56
|
+
* Decoded search object type for a route — the {@link InferRouteParams}
|
|
57
|
+
* twin, and the public spelling that keeps `~search` out of user code.
|
|
58
|
+
*/
|
|
59
|
+
export type InferRouteSearch<R extends AnyRoute> = InferSearchOutput<R["~search"]>;
|
|
55
60
|
/**
|
|
56
61
|
* Accepts promised props and plain objects alike. This width lives on
|
|
57
|
-
* the parse INPUT surface ({@link
|
|
62
|
+
* the parse INPUT surface ({@link RoutePropsLike} and friends), not on the
|
|
58
63
|
* annotation types: every supported Next (peer `>=15`) delivers page props
|
|
59
64
|
* as promises, and Next 15.5's generated `.next/types` page check requires
|
|
60
65
|
* a page's `params` prop to be `Promise<any> | undefined` — a sync arm in
|
|
@@ -105,12 +110,12 @@ export interface PagesRoute<Path extends string, PC extends ParamsConfig<Path>,
|
|
|
105
110
|
*/
|
|
106
111
|
parseContext(context: PagesContext): {
|
|
107
112
|
params: ParamsOutput<Path, PC>;
|
|
108
|
-
search:
|
|
113
|
+
search: InferSearchOutput<SC>;
|
|
109
114
|
};
|
|
110
115
|
/** {@link parseContext} in the safe shape — `safely`'s taxonomy. */
|
|
111
116
|
safeParseContext(context: PagesContext): SafeResult<{
|
|
112
117
|
params: ParamsOutput<Path, PC>;
|
|
113
|
-
search:
|
|
118
|
+
search: InferSearchOutput<SC>;
|
|
114
119
|
}>;
|
|
115
120
|
}
|
|
116
121
|
/**
|
|
@@ -124,7 +129,7 @@ export interface PagesRoute<Path extends string, PC extends ParamsConfig<Path>,
|
|
|
124
129
|
export interface ParamourRegister {
|
|
125
130
|
}
|
|
126
131
|
/** The decoded output type of the codec at key `K`, if one is declared. */
|
|
127
|
-
export type ParamOutput<PC, K extends PropertyKey> = K extends keyof PC ? PC[K] extends AnyCodec ?
|
|
132
|
+
export type ParamOutput<PC, K extends PropertyKey> = K extends keyof PC ? PC[K] extends AnyCodec ? InferCodecOutput<PC[K]> : never : never;
|
|
128
133
|
/**
|
|
129
134
|
* Params schema shape for a path: one codec per dynamic segment name. The
|
|
130
135
|
* codec describes ONE segment element (D5/D6) — arrays come from the segment
|
|
@@ -160,7 +165,7 @@ export interface ParamsProps {
|
|
|
160
165
|
* objects — see {@link MaybePromise} for why the annotation type is
|
|
161
166
|
* promise-only while the parse input stays wide.
|
|
162
167
|
*/
|
|
163
|
-
export interface
|
|
168
|
+
export interface ParamsPropsLike {
|
|
164
169
|
readonly params?: MaybePromise<ParamsSource>;
|
|
165
170
|
}
|
|
166
171
|
/** Every dynamic segment name in the path literal. */
|
|
@@ -206,7 +211,8 @@ export type RegisteredStaticRoutePaths = [PresentRegisteredPaths] extends [
|
|
|
206
211
|
* {@link AppRoute} / {@link PagesRoute} — gating it via the interface split
|
|
207
212
|
* makes the wrong surface ABSENT, not just ill-typed. `~`-prefixed members
|
|
208
213
|
* are runtime-internal, not public API — same convention as codecs;
|
|
209
|
-
* `@paramour
|
|
214
|
+
* the lockstep `@paramour-js/*` packages are blessed consumers, user code is
|
|
215
|
+
* not — and these members are outside semver.
|
|
210
216
|
*/
|
|
211
217
|
export interface Route<Path extends string, PC extends ParamsConfig<Path>, SC extends SearchSlot, R extends RouterKind = RouterKind> {
|
|
212
218
|
readonly path: Path;
|
|
@@ -244,7 +250,7 @@ export type RouteConfig<Path extends string, PC extends ParamsConfig<Path>, SC e
|
|
|
244
250
|
export interface RouteProps extends ParamsProps, SearchProps {
|
|
245
251
|
}
|
|
246
252
|
/** What `parse`/`safeParse` ACCEPT: {@link RouteProps} plus sync props. */
|
|
247
|
-
export interface
|
|
253
|
+
export interface RoutePropsLike extends ParamsPropsLike, SearchPropsLike {
|
|
248
254
|
}
|
|
249
255
|
/** Which router a route belongs to — the value of the `~router` brand. */
|
|
250
256
|
export type RouterKind = "app" | "pages";
|
|
@@ -252,13 +258,15 @@ export type RouterKind = "app" | "pages";
|
|
|
252
258
|
* Status-discriminated result shape, unified with the pages hooks'
|
|
253
259
|
* `RouterResult` (which extends this union by one `pending` member):
|
|
254
260
|
* `if (result.status === "error")` narrows both arms, and both routers'
|
|
255
|
-
* results destructure identically.
|
|
261
|
+
* results destructure identically. `E` narrows the error arm on surfaces
|
|
262
|
+
* that can only fail one way — params-only surfaces carry
|
|
263
|
+
* `ParamsDecodeError`, search-only ones `SearchDecodeError`.
|
|
256
264
|
*/
|
|
257
|
-
export type SafeResult<T> = {
|
|
265
|
+
export type SafeResult<T, E extends RouteDecodeError = RouteDecodeError> = {
|
|
258
266
|
data: T;
|
|
259
267
|
status: "success";
|
|
260
268
|
} | {
|
|
261
|
-
error:
|
|
269
|
+
error: E;
|
|
262
270
|
status: "error";
|
|
263
271
|
};
|
|
264
272
|
/**
|
|
@@ -268,8 +276,8 @@ export type SafeResult<T> = {
|
|
|
268
276
|
export interface SearchProps {
|
|
269
277
|
readonly searchParams?: Promise<ParamsSource>;
|
|
270
278
|
}
|
|
271
|
-
/** Sync-accepting twin of {@link SearchProps} — see {@link
|
|
272
|
-
export interface
|
|
279
|
+
/** Sync-accepting twin of {@link SearchProps} — see {@link ParamsPropsLike}. */
|
|
280
|
+
export interface SearchPropsLike {
|
|
273
281
|
readonly searchParams?: MaybePromise<ParamsSource>;
|
|
274
282
|
}
|
|
275
283
|
/**
|
package/dist/route.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describeType, foreignMessage, ParamourError, ParamsDecodeError, SearchDecodeError, } from "./errors.js";
|
|
2
2
|
import { decodeParams, tokenizePath, } from "./path.js";
|
|
3
|
-
import {
|
|
3
|
+
import { decodeSearchSlot, } from "./search.js";
|
|
4
4
|
/**
|
|
5
5
|
* Defines an App Router route: the URL-shaped path literal plus its
|
|
6
6
|
* param/search codec configs. Validates the literal eagerly — fail-fast at
|
|
@@ -19,7 +19,7 @@ export function defineAppRoute(path, config) {
|
|
|
19
19
|
const decodedParams = decodeParams(route, paramsSource ?? {});
|
|
20
20
|
return {
|
|
21
21
|
params: decodedParams,
|
|
22
|
-
search:
|
|
22
|
+
search: decodeSearchSlot(route["~search"], searchSource ?? {}, route.path),
|
|
23
23
|
};
|
|
24
24
|
},
|
|
25
25
|
async parseParams(props) {
|
|
@@ -28,7 +28,7 @@ export function defineAppRoute(path, config) {
|
|
|
28
28
|
},
|
|
29
29
|
async parseSearch(props) {
|
|
30
30
|
const source = await awaitProp(props.searchParams);
|
|
31
|
-
return
|
|
31
|
+
return decodeSearchSlot(route["~search"], source ?? {}, route.path);
|
|
32
32
|
},
|
|
33
33
|
safeParse(props) {
|
|
34
34
|
return safely(() => route.parse(props));
|
|
@@ -71,7 +71,7 @@ export function definePagesRoute(path, config) {
|
|
|
71
71
|
});
|
|
72
72
|
return {
|
|
73
73
|
params: decodedParams,
|
|
74
|
-
search:
|
|
74
|
+
search: decodeSearchSlot(route["~search"], searchSource, route.path),
|
|
75
75
|
};
|
|
76
76
|
},
|
|
77
77
|
safeParseContext(context) {
|
|
@@ -173,7 +173,9 @@ function routeData(router, path, config) {
|
|
|
173
173
|
/**
|
|
174
174
|
* Wraps a throwing parse into the status-discriminated shape.
|
|
175
175
|
* Only decode failures become the `error` arm; source-contract violations
|
|
176
|
-
* and rebranded foreign errors stay loud.
|
|
176
|
+
* and rebranded foreign errors stay loud. `E` is the caller's claim about
|
|
177
|
+
* which decode error `run` can throw — params-only runs never throw
|
|
178
|
+
* `SearchDecodeError` and vice versa, which is what makes the cast sound.
|
|
177
179
|
*/
|
|
178
180
|
async function safely(run) {
|
|
179
181
|
try {
|
|
@@ -182,7 +184,7 @@ async function safely(run) {
|
|
|
182
184
|
catch (error) {
|
|
183
185
|
if (error instanceof ParamsDecodeError ||
|
|
184
186
|
error instanceof SearchDecodeError) {
|
|
185
|
-
return { error, status: "error" };
|
|
187
|
+
return { error: error, status: "error" };
|
|
186
188
|
}
|
|
187
189
|
throw error;
|
|
188
190
|
}
|
package/dist/safe-decode.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnyRoute, InferRouteParams, SafeResult } from "./route.js";
|
|
2
|
+
import { ParamsDecodeError, SearchDecodeError } from "./errors.js";
|
|
2
3
|
import { type DecodeParamsOptions, type ParamsSource } from "./path.js";
|
|
3
|
-
import { type
|
|
4
|
+
import { type InferSearchOutput, type SearchSlot, type SearchSource, type SlotOf } from "./search.js";
|
|
4
5
|
/**
|
|
5
6
|
* Sync `SafeResult` twins of {@link decodeParams} / {@link decodeSearch},
|
|
6
7
|
* carrying the route methods' safe-parse stance down to the
|
|
@@ -12,6 +13,9 @@ import { type SearchOutputOf, type SearchSource } from "./search.js";
|
|
|
12
13
|
* unchanged.
|
|
13
14
|
*/
|
|
14
15
|
/** Decoded route params as a `SafeResult` (discriminated on `status`). */
|
|
15
|
-
export declare function safeDecodeParams<R extends AnyRoute>(route: R, source: ParamsSource, options?: DecodeParamsOptions): SafeResult<InferRouteParams<R
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
export declare function safeDecodeParams<R extends AnyRoute>(route: R, source: ParamsSource, options?: DecodeParamsOptions): SafeResult<InferRouteParams<R>, ParamsDecodeError>;
|
|
17
|
+
/**
|
|
18
|
+
* Decoded search params as a `SafeResult` (discriminated on `status`).
|
|
19
|
+
* `target` is a route or a bare `search:` slot, as for {@link decodeSearch}.
|
|
20
|
+
*/
|
|
21
|
+
export declare function safeDecodeSearch<T extends AnyRoute | SearchSlot>(target: T, source: SearchSource): SafeResult<InferSearchOutput<SlotOf<T>>, SearchDecodeError>;
|
package/dist/safe-decode.js
CHANGED
|
@@ -22,18 +22,13 @@ export function safeDecodeParams(route, source, options) {
|
|
|
22
22
|
throw error;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Decoded search params as a `SafeResult` (discriminated on `status`).
|
|
27
|
+
* `target` is a route or a bare `search:` slot, as for {@link decodeSearch}.
|
|
28
|
+
*/
|
|
29
|
+
export function safeDecodeSearch(target, source) {
|
|
27
30
|
try {
|
|
28
|
-
|
|
29
|
-
// public type — but AnyRoute erases its SC to `any`, so for a still-
|
|
30
|
-
// generic R the call's value side reduces to `unknown` while the
|
|
31
|
-
// annotation side stays deferred. The cast bridges that inference gap to
|
|
32
|
-
// the SAME (correct) type.
|
|
33
|
-
return {
|
|
34
|
-
data: decodeSearch(route["~search"], source, route.path),
|
|
35
|
-
status: "success",
|
|
36
|
-
};
|
|
31
|
+
return { data: decodeSearch(target, source), status: "success" };
|
|
37
32
|
}
|
|
38
33
|
catch (error) {
|
|
39
34
|
if (error instanceof SearchDecodeError)
|
package/dist/search.d.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import type { AnyCodec,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `| undefined`
|
|
12
|
-
* `exactOptionalPropertyTypes` without key-by-key
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
2
|
+
import type { AnyCodec, InferCodecOutput, PresenceOf } from "./codec.js";
|
|
3
|
+
import type { AnyRoute } from "./route.js";
|
|
4
|
+
/**
|
|
5
|
+
* href / encode input of a `search:` slot (SS6). A codec map: required
|
|
6
|
+
* presence stays required; optional and defaulted keys may be omitted
|
|
7
|
+
* (D4). Array (arity-"many") keys may also be omitted: absent and [] are
|
|
8
|
+
* the same wire state (S6/P6), so requiring `tags: []` ceremony would be
|
|
9
|
+
* pure noise. Omittable keys also admit an EXPLICIT `undefined` —
|
|
10
|
+
* encodeSearch already treats that value as absent (S3), and without the
|
|
11
|
+
* `| undefined` widening a decoded {@link InferSearchOutput} could not flow
|
|
12
|
+
* back into href under `exactOptionalPropertyTypes` without key-by-key
|
|
13
|
+
* reassembly. A `RawSearch` slot accepts the raw wire record instead (SS5 —
|
|
14
|
+
* the schema never runs on encode, so there's no encode-side type to infer
|
|
15
|
+
* from it).
|
|
16
|
+
*/
|
|
17
|
+
export type InferSearchInput<SC extends SearchSlot> = SC extends RawSearch<StandardSchemaV1> ? Record<string, string | string[]> : SC extends SearchConfig ? CodecMapInput<SC> : never;
|
|
18
|
+
/**
|
|
19
|
+
* Decoded output of a `search:` slot (SS6). A codec map: every declared key
|
|
20
|
+
* is PRESENT on the object; optional presence contributes `| undefined` to
|
|
21
|
+
* the value type (D4). A `RawSearch` slot's output is the schema's own
|
|
22
|
+
* inferred output.
|
|
23
|
+
*/
|
|
24
|
+
export type InferSearchOutput<SC extends SearchSlot> = SC extends RawSearch<infer S> ? StandardSchemaV1.InferOutput<S> : SC extends SearchConfig ? CodecMapOutput<SC> : never;
|
|
26
25
|
/**
|
|
27
26
|
* The whole-object search escape hatch (SS1/SS2): wraps a bare
|
|
28
27
|
* Standard Schema in a branded marker so `search:` config discrimination is
|
|
@@ -37,25 +36,9 @@ export interface RawSearch<S extends StandardSchemaV1> {
|
|
|
37
36
|
/** A search-params schema: key → codec. */
|
|
38
37
|
export type SearchConfig = Record<string, AnyCodec>;
|
|
39
38
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* existing `InferSearchInput` behavior. Module-exported for route.ts/href.ts,
|
|
44
|
-
* not barrel-exported — same precedent as `encodeComponent`/`readInputValue`.
|
|
45
|
-
*/
|
|
46
|
-
export type SearchInputOf<SC> = SC extends RawSearch<StandardSchemaV1> ? Record<string, string | string[]> : SC extends SearchConfig ? InferSearchInput<SC> : never;
|
|
47
|
-
/**
|
|
48
|
-
* Parse-output side of a `search:` config (SS6): a `RawSearch`
|
|
49
|
-
* route's output is the schema's own inferred output; a codec map keeps its
|
|
50
|
-
* existing `InferSearchOutput` behavior. Module-exported for
|
|
51
|
-
* route.ts/href.ts, not barrel-exported.
|
|
52
|
-
*/
|
|
53
|
-
export type SearchOutputOf<SC> = SC extends RawSearch<infer S> ? StandardSchemaV1.InferOutput<S> : SC extends SearchConfig ? InferSearchOutput<SC> : never;
|
|
54
|
-
/**
|
|
55
|
-
* The `search:` config slot's full type (SS2): a codec map (the
|
|
56
|
-
* main road) or a `RawSearch` marker (the escape hatch). Internal — not
|
|
57
|
-
* barrel-exported; `Route`/`RouteConfig`/`HrefArgs` consume it as their `SC`
|
|
58
|
-
* bound.
|
|
39
|
+
* The `search:` config slot's full type (SS2): a codec map (the main road)
|
|
40
|
+
* or a `RawSearch` marker (the escape hatch). Public so route wrappers can
|
|
41
|
+
* bound their own `SC` parameter the way `define*Route` does.
|
|
59
42
|
*/
|
|
60
43
|
export type SearchSlot = RawSearch<StandardSchemaV1> | SearchConfig;
|
|
61
44
|
/**
|
|
@@ -64,6 +47,21 @@ export type SearchSlot = RawSearch<StandardSchemaV1> | SearchConfig;
|
|
|
64
47
|
* platform.
|
|
65
48
|
*/
|
|
66
49
|
export type SearchSource = Record<string, string | string[] | undefined> | URLSearchParams;
|
|
50
|
+
/**
|
|
51
|
+
* The `search:` slot a search-function target resolves to: a route's own
|
|
52
|
+
* slot, or the slot itself. Every standalone search function accepts either
|
|
53
|
+
* form (the `nuqsParsers` precedent), so route users never reach for
|
|
54
|
+
* `~search`. Module-exported for safe-decode.ts, not barrel-exported.
|
|
55
|
+
*/
|
|
56
|
+
export type SlotOf<T extends AnyRoute | SearchSlot> = T extends AnyRoute ? T["~search"] : T;
|
|
57
|
+
type CodecMapInput<S extends SearchConfig> = {
|
|
58
|
+
[K in Exclude<keyof S, OptionalInputKeys<S>>]: InferCodecOutput<S[K]>;
|
|
59
|
+
} & {
|
|
60
|
+
[K in OptionalInputKeys<S>]?: InferCodecOutput<S[K]> | undefined;
|
|
61
|
+
};
|
|
62
|
+
type CodecMapOutput<S extends SearchConfig> = {
|
|
63
|
+
[K in keyof S]: PresenceOf<S[K]> extends "optional" ? InferCodecOutput<S[K]> | undefined : InferCodecOutput<S[K]>;
|
|
64
|
+
};
|
|
67
65
|
type OptionalInputKeys<S extends SearchConfig> = {
|
|
68
66
|
[K in keyof S]: S[K]["~arity"] extends "many" ? K : PresenceOf<S[K]> extends "required" ? never : K;
|
|
69
67
|
}[keyof S];
|
|
@@ -84,11 +82,18 @@ export declare function buildSearchString(pairs: readonly (readonly [string, str
|
|
|
84
82
|
* path instead: every source key reaches the schema (P8 does not apply
|
|
85
83
|
* there — the schema owns stripping or passing through extras).
|
|
86
84
|
*
|
|
87
|
-
* `
|
|
88
|
-
*
|
|
89
|
-
*
|
|
85
|
+
* `target` is a route or a bare `search:` slot. A route anchors a thrown
|
|
86
|
+
* {@link SearchDecodeError} to its path pattern; a bare slot (nuqs,
|
|
87
|
+
* devtools, middleware snippets) leaves the error route-less.
|
|
88
|
+
*/
|
|
89
|
+
export declare function decodeSearch<T extends AnyRoute | SearchSlot>(target: T, source: SearchSource): InferSearchOutput<SlotOf<T>>;
|
|
90
|
+
/**
|
|
91
|
+
* {@link decodeSearch} on an already-resolved slot. Module-exported for the
|
|
92
|
+
* route methods, whose generic `SC` is the slot itself — going through the
|
|
93
|
+
* route-or-slot entry would re-derive it as `SlotOf<AppRoute<…>>`, which
|
|
94
|
+
* doesn't reduce back to `SC` inside a generic body.
|
|
90
95
|
*/
|
|
91
|
-
export declare function
|
|
96
|
+
export declare function decodeSearchSlot<S extends SearchSlot>(config: S, source: SearchSource, routePath: null | string): InferSearchOutput<S>;
|
|
92
97
|
/**
|
|
93
98
|
* encodeURIComponent throws a raw URIError on lone surrogates; wrap it so
|
|
94
99
|
* the documented "every error is a ParamourError" contract holds (S7).
|
|
@@ -114,7 +119,7 @@ export declare function encodeComponent(text: string): string;
|
|
|
114
119
|
* serializer exists for a whole-object schema, so the caller's record goes
|
|
115
120
|
* straight to the byte layer and the schema never runs on encode.
|
|
116
121
|
*/
|
|
117
|
-
export declare function encodeSearch<
|
|
122
|
+
export declare function encodeSearch<T extends AnyRoute | SearchSlot>(target: T, input: InferSearchInput<SlotOf<T>>): [string, string][];
|
|
118
123
|
/**
|
|
119
124
|
* Runtime discriminant for the `search:` slot (SS2): probes the
|
|
120
125
|
* `~kind` marker's VALUE, which is unambiguous against a codec map — a map
|
|
@@ -171,7 +176,7 @@ export declare function readInputValue(values: Record<string, unknown>, key: str
|
|
|
171
176
|
*/
|
|
172
177
|
export declare function requireSearchConfig(config: SearchSlot): void;
|
|
173
178
|
/** Convenience: encode + build in one step. */
|
|
174
|
-
export declare function searchToString<
|
|
179
|
+
export declare function searchToString<T extends AnyRoute | SearchSlot>(target: T, input: InferSearchInput<SlotOf<T>>): string;
|
|
175
180
|
/**
|
|
176
181
|
* Invokes a codec's serializer and enforces its string contract: a custom
|
|
177
182
|
* codec written in plain JS can return undefined, which would otherwise
|
package/dist/search.js
CHANGED
|
@@ -24,14 +24,24 @@ export function buildSearchString(pairs) {
|
|
|
24
24
|
* path instead: every source key reaches the schema (P8 does not apply
|
|
25
25
|
* there — the schema owns stripping or passing through extras).
|
|
26
26
|
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* `target` is a route or a bare `search:` slot. A route anchors a thrown
|
|
28
|
+
* {@link SearchDecodeError} to its path pattern; a bare slot (nuqs,
|
|
29
|
+
* devtools, middleware snippets) leaves the error route-less.
|
|
30
30
|
*/
|
|
31
|
-
export function decodeSearch(
|
|
31
|
+
export function decodeSearch(target, source) {
|
|
32
|
+
const [config, routePath] = resolveSearchTarget(target);
|
|
33
|
+
return decodeSearchSlot(config, source, routePath);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* {@link decodeSearch} on an already-resolved slot. Module-exported for the
|
|
37
|
+
* route methods, whose generic `SC` is the slot itself — going through the
|
|
38
|
+
* route-or-slot entry would re-derive it as `SlotOf<AppRoute<…>>`, which
|
|
39
|
+
* doesn't reduce back to `SC` inside a generic body.
|
|
40
|
+
*/
|
|
41
|
+
export function decodeSearchSlot(config, source, routePath) {
|
|
32
42
|
requireSearchConfig(config);
|
|
33
43
|
if (isRawSearch(config)) {
|
|
34
|
-
return decodeRawSearch(config, source, routePath
|
|
44
|
+
return decodeRawSearch(config, source, routePath);
|
|
35
45
|
}
|
|
36
46
|
// The conditional SearchSlot doesn't narrow inside the generic body once
|
|
37
47
|
// the RawSearch branch returns (S stays a generic type parameter); this
|
|
@@ -132,7 +142,7 @@ export function decodeSearch(config, source, routePath) {
|
|
|
132
142
|
}
|
|
133
143
|
}
|
|
134
144
|
if (issues.length > 0) {
|
|
135
|
-
throw new SearchDecodeError(issues, routePath
|
|
145
|
+
throw new SearchDecodeError(issues, routePath);
|
|
136
146
|
}
|
|
137
147
|
return Object.fromEntries(entries);
|
|
138
148
|
}
|
|
@@ -163,7 +173,8 @@ export function encodeComponent(text) {
|
|
|
163
173
|
* serializer exists for a whole-object schema, so the caller's record goes
|
|
164
174
|
* straight to the byte layer and the schema never runs on encode.
|
|
165
175
|
*/
|
|
166
|
-
export function encodeSearch(
|
|
176
|
+
export function encodeSearch(target, input) {
|
|
177
|
+
const [config] = resolveSearchTarget(target);
|
|
167
178
|
requireSearchConfig(config);
|
|
168
179
|
if (isRawSearch(config)) {
|
|
169
180
|
return encodeRawSearch(input);
|
|
@@ -302,8 +313,8 @@ export function requireSearchConfig(config) {
|
|
|
302
313
|
}
|
|
303
314
|
}
|
|
304
315
|
/** Convenience: encode + build in one step. */
|
|
305
|
-
export function searchToString(
|
|
306
|
-
return buildSearchString(encodeSearch(
|
|
316
|
+
export function searchToString(target, input) {
|
|
317
|
+
return buildSearchString(encodeSearch(target, input));
|
|
307
318
|
}
|
|
308
319
|
/**
|
|
309
320
|
* Invokes a codec's serializer and enforces its string contract: a custom
|
|
@@ -533,3 +544,21 @@ function requireRawSearchString(key, value) {
|
|
|
533
544
|
}
|
|
534
545
|
return value;
|
|
535
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Splits a search-function target into its slot and the path that anchors
|
|
549
|
+
* errors. Routes are recognized by their `~search`/`~segments` members —
|
|
550
|
+
* `~`-prefixed keys are reserved, so no codec map or `RawSearch` marker can
|
|
551
|
+
* carry both. Anything else passes through for `requireSearchConfig` to
|
|
552
|
+
* validate.
|
|
553
|
+
*/
|
|
554
|
+
function resolveSearchTarget(target) {
|
|
555
|
+
const untrusted = target;
|
|
556
|
+
if (typeof untrusted === "object" &&
|
|
557
|
+
untrusted !== null &&
|
|
558
|
+
"~search" in untrusted &&
|
|
559
|
+
"~segments" in untrusted) {
|
|
560
|
+
const route = target;
|
|
561
|
+
return [route["~search"], route.path];
|
|
562
|
+
}
|
|
563
|
+
return [target, null];
|
|
564
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import type { AnyRoute } from "./route.js";
|
|
3
|
-
import { type SearchOutputOf } from "./search.js";
|
|
2
|
+
import type { AnyRoute, InferRouteSearch } from "./route.js";
|
|
4
3
|
/**
|
|
5
4
|
* Standard Schema generate-OUT: exports a route's `search:` config as a
|
|
6
5
|
* spec-compliant Standard Schema. The mirror of schema.ts, which runs
|
|
@@ -14,7 +13,7 @@ import { type SearchOutputOf } from "./search.js";
|
|
|
14
13
|
* shape. `types` is carried by this annotation alone; the spec reads it at
|
|
15
14
|
* the type level only, so no runtime key exists.
|
|
16
15
|
*/
|
|
17
|
-
export type StandardSearchSchema<
|
|
16
|
+
export type StandardSearchSchema<R extends AnyRoute> = StandardSchemaV1<Record<string, string | string[] | undefined>, InferRouteSearch<R>>;
|
|
18
17
|
/**
|
|
19
18
|
* Exports a route's `search:` config as the URL wire contract in Standard
|
|
20
19
|
* Schema form, for consumers like tRPC inputs or TanStack `validateSearch`.
|
|
@@ -24,4 +23,4 @@ export type StandardSearchSchema<SC> = StandardSchemaV1<Record<string, string |
|
|
|
24
23
|
* reject (P5). No coercion, ever: the schema accepts wire strings (`"42"`),
|
|
25
24
|
* not decoded values (`42`).
|
|
26
25
|
*/
|
|
27
|
-
export declare function standardSearchSchema<R extends AnyRoute>(route: R): StandardSearchSchema<R
|
|
26
|
+
export declare function standardSearchSchema<R extends AnyRoute>(route: R): StandardSearchSchema<R>;
|