paramour 0.0.0 → 0.2.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 +76 -0
- package/dist/codec.js +110 -0
- package/dist/describe.d.ts +63 -0
- package/dist/describe.js +75 -0
- package/dist/errors.d.ts +73 -0
- package/dist/errors.js +157 -0
- package/dist/href.d.ts +79 -0
- package/dist/href.js +32 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/p.d.ts +23 -0
- package/dist/p.js +265 -0
- package/dist/path.d.ts +108 -0
- package/dist/path.js +439 -0
- package/dist/route.d.ts +293 -0
- package/dist/route.js +210 -0
- package/dist/safe-decode.d.ts +16 -0
- package/dist/safe-decode.js +42 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +16 -0
- package/dist/search.d.ts +155 -0
- package/dist/search.js +474 -0
- package/dist/standard-schema.d.ts +27 -0
- package/dist/standard-schema.js +77 -0
- package/package.json +28 -4
- package/LICENSE +0 -21
- package/README.md +0 -28
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { type AnyCodec, type Arity, type Codec, type OutputOf, type ParamCodec, type Presence, type PresenceOf, } from "./codec.js";
|
|
2
|
+
export { type CodecDefaultDescription, type CodecDescription, describeCodec, describeRoute, type ParamDescription, type RouteDescription, type SearchDescription, } from "./describe.js";
|
|
3
|
+
export { type Issue, ParamourError, ParamsDecodeError, ParseError, type RouteDecodeError, SearchDecodeError, SearchSourceError, SerializeError, } from "./errors.js";
|
|
4
|
+
export { href, type Href, type HrefArgs, type InferHrefInput, type StaticHrefOptions, } from "./href.js";
|
|
5
|
+
export { p } from "./p.js";
|
|
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 RegisteredAppRoutePaths, type RegisteredPagesRoutePaths, type RegisteredStaticAppRoutePaths, type RegisteredStaticPagesRoutePaths, type RegisteredStaticRoutePaths, type Route, type RouteProps, type RouterKind, type SafeResult, type SearchProps, } from "./route.js";
|
|
8
|
+
export { safeDecodeParams, safeDecodeSearch } from "./safe-decode.js";
|
|
9
|
+
export { buildSearchString, decodeSearch, encodeSearch, type InferSearchInput, type InferSearchOutput, rawSearch, type RawSearch, type SearchConfig, type SearchOutputOf, type SearchSource, searchToString, } from "./search.js";
|
|
10
|
+
export { standardSearchSchema, type StandardSearchSchema, } from "./standard-schema.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {} from "./codec.js";
|
|
2
|
+
export { describeCodec, describeRoute, } from "./describe.js";
|
|
3
|
+
export { ParamourError, ParamsDecodeError, ParseError, SearchDecodeError, SearchSourceError, SerializeError, } from "./errors.js";
|
|
4
|
+
export { href, } from "./href.js";
|
|
5
|
+
export { p } from "./p.js";
|
|
6
|
+
export { buildPath, decodeParams, encodeParams, encodeStaticParams, } from "./path.js";
|
|
7
|
+
export { defineAppRoute, definePagesRoute, } from "./route.js";
|
|
8
|
+
export { safeDecodeParams, safeDecodeSearch } from "./safe-decode.js";
|
|
9
|
+
export { buildSearchString, decodeSearch, encodeSearch, rawSearch, searchToString, } from "./search.js";
|
|
10
|
+
export { standardSearchSchema, } from "./standard-schema.js";
|
package/dist/p.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { type Codec } from "./codec.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `p.*` wire-codec builders (DESIGN §5 layer 1, design-02 D9).
|
|
5
|
+
* Each codec defines how one value crosses the URL boundary, both directions.
|
|
6
|
+
*/
|
|
7
|
+
export declare const p: {
|
|
8
|
+
boolean(): Codec<boolean>;
|
|
9
|
+
custom<Out>(codec: {
|
|
10
|
+
/** Reflection name shown by describeCodec/`paramour list` (default "custom"). */
|
|
11
|
+
label?: string;
|
|
12
|
+
parse: (raw: string) => Out;
|
|
13
|
+
serialize: (value: Out) => string;
|
|
14
|
+
}): Codec<Out>;
|
|
15
|
+
enum<const M extends readonly [string, ...string[]]>(members: M): Codec<M[number]>;
|
|
16
|
+
integer<S extends StandardSchemaV1<number, number>>(schema?: S): Codec<S extends undefined ? number : StandardSchemaV1.InferOutput<S>>;
|
|
17
|
+
isoDate(): Codec<Date>;
|
|
18
|
+
json<S extends StandardSchemaV1>(schema: S): Codec<StandardSchemaV1.InferOutput<S>>;
|
|
19
|
+
number<S extends StandardSchemaV1<number, number>>(schema?: S): Codec<S extends undefined ? number : StandardSchemaV1.InferOutput<S>>;
|
|
20
|
+
string<S extends StandardSchemaV1<string, string>>(schema?: S): Codec<S extends undefined ? string : StandardSchemaV1.InferOutput<S>>;
|
|
21
|
+
stringArray(): Codec<string[], "required", false, "many">;
|
|
22
|
+
timestamp(): Codec<Date>;
|
|
23
|
+
};
|
package/dist/p.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { createCodec } from "./codec.js";
|
|
2
|
+
import { foreignMessage, ParseError, rebrandForeign, SerializeError, showValue, } from "./errors.js";
|
|
3
|
+
import { runStandardSchemaSync } from "./schema.js";
|
|
4
|
+
// Wire grammars per wire-format spec §4. `Number()` alone is too loose
|
|
5
|
+
// (accepts hex, trims whitespace), hence explicit anchored patterns.
|
|
6
|
+
const INTEGER_RE = /^-?\d+$/;
|
|
7
|
+
const NUMBER_RE = /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;
|
|
8
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
9
|
+
// Canonical emit is Date#toISOString (milliseconds always); parse tolerates
|
|
10
|
+
// missing milliseconds. UTC (`Z`) only — offsets are rejected in v0.1.
|
|
11
|
+
const TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
12
|
+
/**
|
|
13
|
+
* Serialize-side Date guard. Years outside 0000–9999 are rejected:
|
|
14
|
+
* toISOString switches to the expanded ±6-digit-year form there, which the
|
|
15
|
+
* wire grammars (§4) cannot represent — better a loud SerializeError than a
|
|
16
|
+
* URL that can never round-trip.
|
|
17
|
+
*/
|
|
18
|
+
function expectSerializableDate(value) {
|
|
19
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
20
|
+
throw new SerializeError("Expected a valid Date");
|
|
21
|
+
}
|
|
22
|
+
const year = value.getUTCFullYear();
|
|
23
|
+
if (year < 0 || year > 9999) {
|
|
24
|
+
throw new SerializeError(`Date year ${String(year)} is outside the representable 0000-9999 range`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
// Array.from, not .map: the issues array belongs to the validator and may be
|
|
29
|
+
// an Array subclass whose Symbol.species constructor mangles a mapped result
|
|
30
|
+
// (see the note in search.ts's decodeRawSearch).
|
|
31
|
+
function joinIssues(issues) {
|
|
32
|
+
return Array.from(issues, (issue) => issue.message).join("; ");
|
|
33
|
+
}
|
|
34
|
+
function parseIntegerElement(raw) {
|
|
35
|
+
if (!INTEGER_RE.test(raw)) {
|
|
36
|
+
throw new ParseError(`"${raw}" is not an integer`);
|
|
37
|
+
}
|
|
38
|
+
const value = Number(raw);
|
|
39
|
+
if (!Number.isSafeInteger(value)) {
|
|
40
|
+
throw new ParseError(`"${raw}" is outside the safe integer range`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function parseNumberElement(raw) {
|
|
45
|
+
if (!NUMBER_RE.test(raw)) {
|
|
46
|
+
throw new ParseError(`"${raw}" is not a number`);
|
|
47
|
+
}
|
|
48
|
+
const value = Number(raw);
|
|
49
|
+
if (!Number.isFinite(value)) {
|
|
50
|
+
throw new ParseError(`"${raw}" is not a finite number`);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function refine(schema, value) {
|
|
55
|
+
const result = runStandardSchemaSync(schema, value);
|
|
56
|
+
if (result.issues) {
|
|
57
|
+
throw new ParseError(`Schema validation failed: ${joinIssues(result.issues)}`);
|
|
58
|
+
}
|
|
59
|
+
return result.value;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Serialize-side twin of {@link refine}: schema-invalid in-memory values must
|
|
63
|
+
* fail loudly at link-build time, not on the next navigation. The schema's
|
|
64
|
+
* returned value is what goes on the wire, so normalizing schemas emit
|
|
65
|
+
* canonical form. Transforming (In≠Out) schemas are parse-only by design —
|
|
66
|
+
* their output fails input validation here; use `p.custom` for bidirectional
|
|
67
|
+
* transforms.
|
|
68
|
+
*/
|
|
69
|
+
function refineForSerialize(schema, value) {
|
|
70
|
+
const result = runStandardSchemaSync(schema, value);
|
|
71
|
+
if (result.issues) {
|
|
72
|
+
throw new SerializeError(`Schema validation failed: ${joinIssues(result.issues)}`);
|
|
73
|
+
}
|
|
74
|
+
return result.value;
|
|
75
|
+
}
|
|
76
|
+
function serializeFiniteNumber(value) {
|
|
77
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
78
|
+
throw new SerializeError(`Expected a finite number, got ${showValue(value)}`);
|
|
79
|
+
}
|
|
80
|
+
return String(value);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* JSON.stringify throws raw TypeErrors (circular refs, BigInt) and lets
|
|
84
|
+
* toJSON() exceptions escape; wrap them so the ParamourError contract holds.
|
|
85
|
+
*/
|
|
86
|
+
function stringifyJson(value) {
|
|
87
|
+
return rebrandForeign(() => JSON.stringify(value), (error) => new SerializeError("Value is not JSON-serializable", { cause: error }));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The `p.*` wire-codec builders (DESIGN §5 layer 1, design-02 D9).
|
|
91
|
+
* Each codec defines how one value crosses the URL boundary, both directions.
|
|
92
|
+
*/
|
|
93
|
+
export const p = {
|
|
94
|
+
boolean() {
|
|
95
|
+
return createCodec({
|
|
96
|
+
kind: "boolean",
|
|
97
|
+
parseElement: (raw) => {
|
|
98
|
+
if (raw === "true")
|
|
99
|
+
return true;
|
|
100
|
+
if (raw === "false")
|
|
101
|
+
return false;
|
|
102
|
+
throw new ParseError(`"${raw}" is not "true" or "false"`);
|
|
103
|
+
},
|
|
104
|
+
serializeElement: (value) => {
|
|
105
|
+
if (typeof value !== "boolean") {
|
|
106
|
+
throw new SerializeError(`Expected a boolean, got ${showValue(value)}`);
|
|
107
|
+
}
|
|
108
|
+
return value ? "true" : "false";
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
custom(codec) {
|
|
113
|
+
// Paramour's own errors are never downgraded: ANY ParamourError thrown
|
|
114
|
+
// by user parse/serialize code — config-level failures (async schema,
|
|
115
|
+
// builder misuse) but also value-level errors from reused paramour
|
|
116
|
+
// helpers — passes through loud, bypassing .catch() recovery and per-key
|
|
117
|
+
// aggregation. .catch() recovers foreign parse failures only, which
|
|
118
|
+
// rebrandForeign normalizes to ParseError so recovery sees them.
|
|
119
|
+
return createCodec({
|
|
120
|
+
...(codec.label === undefined ? {} : { kind: codec.label }),
|
|
121
|
+
parseElement: (raw) => rebrandForeign(() => codec.parse(raw), (error) => new ParseError(foreignMessage(error), { cause: error })),
|
|
122
|
+
serializeElement: (value) => rebrandForeign(() => codec.serialize(value), (error) => new SerializeError(foreignMessage(error), { cause: error })),
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
enum(members) {
|
|
126
|
+
const set = new Set(members);
|
|
127
|
+
return createCodec({
|
|
128
|
+
enumMembers: members,
|
|
129
|
+
kind: "enum",
|
|
130
|
+
parseElement: (raw) => {
|
|
131
|
+
if (!set.has(raw)) {
|
|
132
|
+
throw new ParseError(`"${raw}" is not one of: ${members.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
return raw;
|
|
135
|
+
},
|
|
136
|
+
serializeElement: (value) => {
|
|
137
|
+
if (typeof value !== "string" || !set.has(value)) {
|
|
138
|
+
throw new SerializeError(`${showValue(value)} is not one of: ${members.join(", ")}`);
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
integer(schema) {
|
|
145
|
+
return createCodec({
|
|
146
|
+
kind: "integer",
|
|
147
|
+
parseElement: (raw) => {
|
|
148
|
+
const value = parseIntegerElement(raw);
|
|
149
|
+
return schema ? refine(schema, value) : value;
|
|
150
|
+
},
|
|
151
|
+
serializeElement: (value) => {
|
|
152
|
+
const refined = schema ? refineForSerialize(schema, value) : value;
|
|
153
|
+
const serialized = serializeFiniteNumber(refined);
|
|
154
|
+
if (!Number.isSafeInteger(refined)) {
|
|
155
|
+
throw new SerializeError(`${serialized} is not a safe integer`);
|
|
156
|
+
}
|
|
157
|
+
return serialized;
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
isoDate() {
|
|
162
|
+
return createCodec({
|
|
163
|
+
kind: "isoDate",
|
|
164
|
+
parseElement: (raw) => {
|
|
165
|
+
if (!ISO_DATE_RE.test(raw)) {
|
|
166
|
+
throw new ParseError(`"${raw}" is not a YYYY-MM-DD date`);
|
|
167
|
+
}
|
|
168
|
+
// ISO-string construction, not Date.UTC: the latter maps years 0-99
|
|
169
|
+
// to 1900+year. The round-trip comparison rejects days the engine
|
|
170
|
+
// would silently normalize (2026-02-30 → Mar 2).
|
|
171
|
+
const date = new Date(`${raw}T00:00:00.000Z`);
|
|
172
|
+
if (Number.isNaN(date.getTime()) ||
|
|
173
|
+
date.toISOString().slice(0, 10) !== raw) {
|
|
174
|
+
throw new ParseError(`"${raw}" is not a real calendar date`);
|
|
175
|
+
}
|
|
176
|
+
return date;
|
|
177
|
+
},
|
|
178
|
+
serializeElement: (value) => expectSerializableDate(value).toISOString().slice(0, 10),
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
json(schema) {
|
|
182
|
+
return createCodec({
|
|
183
|
+
kind: "json",
|
|
184
|
+
parseElement: (raw) => {
|
|
185
|
+
let parsed;
|
|
186
|
+
try {
|
|
187
|
+
parsed = JSON.parse(raw);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
throw new ParseError(`"${raw}" is not valid JSON`);
|
|
191
|
+
}
|
|
192
|
+
return refine(schema, parsed);
|
|
193
|
+
},
|
|
194
|
+
serializeElement: (value) => {
|
|
195
|
+
const refined = refineForSerialize(schema, value);
|
|
196
|
+
// lib.d.ts types JSON.stringify as always-string, but it returns
|
|
197
|
+
// undefined for undefined/function/symbol inputs.
|
|
198
|
+
const serialized = stringifyJson(refined);
|
|
199
|
+
if (serialized === undefined) {
|
|
200
|
+
throw new SerializeError("Value is not JSON-serializable");
|
|
201
|
+
}
|
|
202
|
+
return serialized;
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
number(schema) {
|
|
207
|
+
return createCodec({
|
|
208
|
+
kind: "number",
|
|
209
|
+
parseElement: (raw) => {
|
|
210
|
+
const value = parseNumberElement(raw);
|
|
211
|
+
return schema ? refine(schema, value) : value;
|
|
212
|
+
},
|
|
213
|
+
serializeElement: (value) => serializeFiniteNumber(schema ? refineForSerialize(schema, value) : value),
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
string(schema) {
|
|
217
|
+
return createCodec({
|
|
218
|
+
kind: "string",
|
|
219
|
+
parseElement: (raw) => (schema ? refine(schema, raw) : raw),
|
|
220
|
+
serializeElement: (value) => {
|
|
221
|
+
const refined = schema ? refineForSerialize(schema, value) : value;
|
|
222
|
+
if (typeof refined !== "string") {
|
|
223
|
+
throw new SerializeError("Expected a string");
|
|
224
|
+
}
|
|
225
|
+
return refined;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
stringArray() {
|
|
230
|
+
return createCodec({
|
|
231
|
+
arity: "many",
|
|
232
|
+
kind: "string",
|
|
233
|
+
parseElement: (raw) => raw,
|
|
234
|
+
serializeElement: (value) => {
|
|
235
|
+
if (typeof value !== "string") {
|
|
236
|
+
throw new SerializeError("Expected an array of strings");
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
timestamp() {
|
|
243
|
+
return createCodec({
|
|
244
|
+
kind: "timestamp",
|
|
245
|
+
parseElement: (raw) => {
|
|
246
|
+
if (!TIMESTAMP_RE.test(raw)) {
|
|
247
|
+
throw new ParseError(`"${raw}" is not an ISO 8601 UTC timestamp`);
|
|
248
|
+
}
|
|
249
|
+
const date = new Date(raw);
|
|
250
|
+
if (Number.isNaN(date.getTime())) {
|
|
251
|
+
throw new ParseError(`"${raw}" is not a real instant`);
|
|
252
|
+
}
|
|
253
|
+
// The engine silently normalizes impossible fields (Feb 30 → Mar 1,
|
|
254
|
+
// 24:00 → next day). Pad the input to canonical millisecond form and
|
|
255
|
+
// require an exact round-trip instead.
|
|
256
|
+
const canonical = raw.replace(/(?:\.(\d{1,3}))?Z$/, (_match, ms) => `.${(ms ?? "").padEnd(3, "0")}Z`);
|
|
257
|
+
if (date.toISOString() !== canonical) {
|
|
258
|
+
throw new ParseError(`"${raw}" is not a real instant`);
|
|
259
|
+
}
|
|
260
|
+
return date;
|
|
261
|
+
},
|
|
262
|
+
serializeElement: (value) => expectSerializableDate(value).toISOString(),
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
};
|
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { AnyRoute, CatchAllNames, InferRouteParams, OptionalCatchAllNames, ParamOutput, SingleParamNames } from "./route.js";
|
|
2
|
+
/**
|
|
3
|
+
* Decode-policy knob for {@link decodeParams} / {@link safeDecodeParams} (R5).
|
|
4
|
+
* `percentDecode` defaults to `true` — the App-Router reality, where Next
|
|
5
|
+
* hands the `params` prop / `useParams()` surface percent-ENCODED values
|
|
6
|
+
* (issues #48058/#64952), so core owns the decode. The pages surfaces are the
|
|
7
|
+
* exception: `useRouter().query` and `getServerSideProps` `ctx.params`/`query`
|
|
8
|
+
* have ALREADY been percent-decoded by Node's querystring layer, so those
|
|
9
|
+
* entry points pass `{ percentDecode: false }` to avoid a double-decode
|
|
10
|
+
* (`/product/a%2520b` → `"a%20b"` from Node → must survive, not decode again).
|
|
11
|
+
* Optional-with-default under `exactOptionalPropertyTypes`: absent or a plain
|
|
12
|
+
* `boolean`, never `undefined`.
|
|
13
|
+
*/
|
|
14
|
+
export interface DecodeParamsOptions {
|
|
15
|
+
readonly percentDecode?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encode-input side of a route's params (RL3's href-input column): `[id]` →
|
|
19
|
+
* `Out` (required), `[...slug]` → `Out[]` (required — `[]` is an R3
|
|
20
|
+
* serialization error), `[[...slug]]` → `Out[]` with an OPTIONAL key, per
|
|
21
|
+
* the spike-01 follow-up under exactOptionalPropertyTypes. Module-level
|
|
22
|
+
* export only; Block 3's `InferHrefInput` builds on it.
|
|
23
|
+
*/
|
|
24
|
+
export type InferParamsInput<R extends AnyRoute> = {
|
|
25
|
+
[K in CatchAllNames<R["path"]>]: ParamOutput<R["~params"], K>[];
|
|
26
|
+
} & {
|
|
27
|
+
[K in OptionalCatchAllNames<R["path"]>]?: ParamOutput<R["~params"], K>[];
|
|
28
|
+
} & {
|
|
29
|
+
[K in SingleParamNames<R["path"]>]: ParamOutput<R["~params"], K>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Return shape of {@link encodeStaticParams}: the per-param wire-value record
|
|
33
|
+
* Next's static-generation surfaces expect — `[id]` → `string`, `[...slug]` →
|
|
34
|
+
* `string[]`, `[[...slug]]` → `string[]` with an OPTIONAL key (absent is the
|
|
35
|
+
* R3 base-path variant). Mapped types carry implicit index signatures, so the
|
|
36
|
+
* result is assignable to `generateStaticParams`' and `getStaticPaths`'
|
|
37
|
+
* params shapes without a cast.
|
|
38
|
+
*/
|
|
39
|
+
export type InferStaticParams<R extends AnyRoute> = Partial<Record<OptionalCatchAllNames<R["path"]>, string[]>> & Record<CatchAllNames<R["path"]>, string[]> & Record<SingleParamNames<R["path"]>, string>;
|
|
40
|
+
/**
|
|
41
|
+
* Value-layer params source (wire spec §1, R5): the shape of Next's `params`
|
|
42
|
+
* prop and `useParams()` return. Contrary to the byte-layer's usual "platform
|
|
43
|
+
* already decoded it" stance, Next hands the App-Router surfaces percent-ENCODED
|
|
44
|
+
* values (Next issues #48058/#64952 — only route.ts handlers decode), so core
|
|
45
|
+
* owns the decode in {@link decodeParams} before applying codec grammars. The
|
|
46
|
+
* pages surfaces are the exception (already Node-decoded); they opt out via
|
|
47
|
+
* {@link DecodeParamsOptions}'s `percentDecode: false`.
|
|
48
|
+
*/
|
|
49
|
+
export type ParamsSource = Record<string, string | string[] | undefined>;
|
|
50
|
+
/** One parsed path segment, as produced by {@link tokenizePath}. */
|
|
51
|
+
export type PathSegment = {
|
|
52
|
+
readonly kind: "catchall" | "optional-catchall" | "single";
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly raw: string;
|
|
55
|
+
} | {
|
|
56
|
+
readonly kind: "static";
|
|
57
|
+
readonly raw: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Builds the path portion of an href (RL5): `/` plus the encoded segments
|
|
61
|
+
* joined with `/`. R2's element joining falls out of the same join as
|
|
62
|
+
* everything else; a fully-elided path (an optional catch-all at the root)
|
|
63
|
+
* yields "/".
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildPath<R extends AnyRoute>(route: R, params: InferParamsInput<R>): string;
|
|
66
|
+
/**
|
|
67
|
+
* Decodes a params source against a route's codecs (RL7), the sync twin of
|
|
68
|
+
* `route.parseParams` — mirrors decodeSearch: per-key {@link Issue}
|
|
69
|
+
* aggregation into {@link ParamsDecodeError}. Shape validation is strict:
|
|
70
|
+
* `[id]` given an array, a catch-all given a string, a missing required key,
|
|
71
|
+
* or a non-string element are recorded issues, and NOT `.catch()`-recoverable
|
|
72
|
+
* — a shape mismatch means the props came from a route this definition
|
|
73
|
+
* doesn't describe. Unknown source keys are never read (P8's spirit; Next
|
|
74
|
+
* includes parent-layout params).
|
|
75
|
+
*/
|
|
76
|
+
export declare function decodeParams<R extends AnyRoute>(route: R, source: ParamsSource, options?: DecodeParamsOptions): InferRouteParams<R>;
|
|
77
|
+
/**
|
|
78
|
+
* Encodes a params input into ordered, already-percent-encoded URL segment
|
|
79
|
+
* strings (RL5) — ONE entry per emitted URL segment: a static segment is
|
|
80
|
+
* emitted verbatim, a single param contributes one entry (R1), a catch-all
|
|
81
|
+
* one per element (R2), an elided optional catch-all none (R3). Codec
|
|
82
|
+
* serialize errors and schema-refinement failures (N9) propagate unchanged,
|
|
83
|
+
* already branded at their own chokepoints.
|
|
84
|
+
*/
|
|
85
|
+
export declare function encodeParams<R extends AnyRoute>(route: R, params: InferParamsInput<R>): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Encodes a params input into the per-param wire-value record the static
|
|
88
|
+
* generation surfaces expect: App Router `generateStaticParams` entries and
|
|
89
|
+
* Pages Router `getStaticPaths` `{ params }` objects (PR10's static story).
|
|
90
|
+
* Same codec serialization and R1–R4 validation as {@link encodeParams},
|
|
91
|
+
* with two deliberate differences: static segments are skipped (Next wants
|
|
92
|
+
* only the dynamic params, keyed by name), and values are NOT percent-encoded
|
|
93
|
+
* — Next percent-encodes static-params values itself when it materializes
|
|
94
|
+
* the concrete URLs, so pre-encoding here would double-encode (the
|
|
95
|
+
* encode-side mirror of R5's decode asymmetry). That also means the S7
|
|
96
|
+
* lone-surrogate brand stays an encodeParams concern: strings are handed to
|
|
97
|
+
* Next verbatim. An elided optional catch-all (R3) OMITS its key — an absent
|
|
98
|
+
* key is the base-path variant on both routers (Pages also accepts
|
|
99
|
+
* undefined/[]; omission is the one spelling valid on both).
|
|
100
|
+
*/
|
|
101
|
+
export declare function encodeStaticParams<R extends AnyRoute>(route: R, params: InferParamsInput<R>): InferStaticParams<R>;
|
|
102
|
+
/**
|
|
103
|
+
* Tokenizes a path literal into segments, throwing ParamourError on every
|
|
104
|
+
* RL1 rejection. Shared by the route constructors (define-time validation)
|
|
105
|
+
* and the R-rule runtime here, so encode/decode never re-derive segment
|
|
106
|
+
* kinds.
|
|
107
|
+
*/
|
|
108
|
+
export declare function tokenizePath(path: string): PathSegment[];
|