@bb-labs/next-router 0.0.1 → 0.0.3
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 +95 -34
- package/dist/client/z-page/index.d.ts +7 -15
- package/dist/client/z-page/index.js +15 -10
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.js +2 -0
- package/dist/common/z-page/infer-params.d.ts +24 -0
- package/dist/common/z-page/resolve-params.d.ts +14 -0
- package/dist/{server/z-page/helpers → common/z-page}/resolve-params.js +2 -2
- package/dist/server/z-page/fns/z-page.d.ts +20 -20
- package/dist/server/z-page/fns/z-page.js +1 -1
- package/dist/server/z-page/index.d.ts +0 -2
- package/dist/server/z-page/index.js +0 -1
- package/dist/server/z-page/utils/setup.d.ts +16 -0
- package/dist/server/z-page/utils/{helper.js → setup.js} +3 -2
- package/dist/server/z-page/{types.d.ts → utils/types.d.ts} +2 -4
- package/dist/utils/to-zod-object.d.ts +4 -0
- package/dist/utils/to-zod-object.js +7 -0
- package/dist/utils/types.d.ts +7 -0
- package/dist/utils/types.js +1 -0
- package/package.json +3 -2
- package/dist/server/z-page/helpers/infer-props.d.ts +0 -12
- package/dist/server/z-page/helpers/resolve-params.d.ts +0 -14
- package/dist/server/z-page/utils/helper.d.ts +0 -17
- /package/dist/{server/z-page/helpers/infer-props.js → common/z-page/infer-params.js} +0 -0
- /package/dist/server/z-page/{types.js → utils/types.js} +0 -0
package/README.md
CHANGED
|
@@ -35,9 +35,9 @@ import { z } from "zod";
|
|
|
35
35
|
|
|
36
36
|
export default zPage({
|
|
37
37
|
params: {
|
|
38
|
-
searchParams:
|
|
38
|
+
searchParams: {
|
|
39
39
|
email: z.string().email(),
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
41
|
},
|
|
42
42
|
handler: ({ searchParams }) => {
|
|
43
43
|
// Already awaited — use directly
|
|
@@ -56,17 +56,18 @@ export default zPage({
|
|
|
56
56
|
When `resolve: false`, params are passed as **promises** — you control when to await.
|
|
57
57
|
|
|
58
58
|
```tsx
|
|
59
|
-
import { zPage
|
|
59
|
+
import { zPage } from "@bb-labs/next-router";
|
|
60
|
+
import { resolveZPageParams } from "@bb-labs/next-router/common";
|
|
60
61
|
import { z } from "zod";
|
|
61
62
|
|
|
62
63
|
export default zPage({
|
|
63
64
|
params: {
|
|
64
|
-
routeParams:
|
|
65
|
-
searchParams:
|
|
65
|
+
routeParams: { id: z.string() },
|
|
66
|
+
searchParams: { page: z.coerce.number().optional() },
|
|
66
67
|
},
|
|
67
68
|
resolve: false,
|
|
68
69
|
handler: async (paramsPromise) => {
|
|
69
|
-
const { routeParams, searchParams } = await
|
|
70
|
+
const { routeParams, searchParams } = await resolveZPageParams(paramsPromise);
|
|
70
71
|
return (
|
|
71
72
|
<div>
|
|
72
73
|
Product {routeParams.id}, Page {searchParams.page}
|
|
@@ -78,24 +79,25 @@ export default zPage({
|
|
|
78
79
|
|
|
79
80
|
**Note:** `onError` and `loadingHandler` are not available when `resolve: false`.
|
|
80
81
|
|
|
81
|
-
### `
|
|
82
|
+
### `resolveZPageParams`
|
|
82
83
|
|
|
83
84
|
Helper function to await and validate params from promise-based handlers. Optionally accepts an `onError` callback.
|
|
84
85
|
|
|
85
86
|
```tsx
|
|
86
|
-
import { zPage
|
|
87
|
+
import { zPage } from "@bb-labs/next-router";
|
|
88
|
+
import { resolveZPageParams } from "@bb-labs/next-router/common";
|
|
87
89
|
import { redirect } from "next/navigation";
|
|
88
90
|
import { z } from "zod";
|
|
89
91
|
|
|
90
92
|
export default zPage({
|
|
91
93
|
params: {
|
|
92
|
-
searchParams:
|
|
94
|
+
searchParams: {
|
|
93
95
|
email: z.string().email(),
|
|
94
|
-
}
|
|
96
|
+
},
|
|
95
97
|
},
|
|
96
98
|
resolve: false,
|
|
97
99
|
handler: async (paramsPromise) => {
|
|
98
|
-
const { searchParams } = await
|
|
100
|
+
const { searchParams } = await resolveZPageParams(paramsPromise, {
|
|
99
101
|
onError: (error) => {
|
|
100
102
|
console.error("Validation failed:", error);
|
|
101
103
|
redirect("/custom-error");
|
|
@@ -112,13 +114,13 @@ You can provide both, only `routeParams`, or only `searchParams` — but never n
|
|
|
112
114
|
|
|
113
115
|
```tsx
|
|
114
116
|
// ✅ Both
|
|
115
|
-
{ params: { routeParams:
|
|
117
|
+
{ params: { routeParams: {...}, searchParams: {...} } }
|
|
116
118
|
|
|
117
119
|
// ✅ Only routeParams
|
|
118
|
-
{ params: { routeParams:
|
|
120
|
+
{ params: { routeParams: {...} } }
|
|
119
121
|
|
|
120
122
|
// ✅ Only searchParams
|
|
121
|
-
{ params: { searchParams:
|
|
123
|
+
{ params: { searchParams: {...} } }
|
|
122
124
|
|
|
123
125
|
// ❌ Neither — TypeScript error
|
|
124
126
|
{ params: {} }
|
|
@@ -138,9 +140,9 @@ Hooks for accessing validated params in client components with `onSuccess` and `
|
|
|
138
140
|
import { useSearchParams } from "@bb-labs/next-router/client";
|
|
139
141
|
import { z } from "zod";
|
|
140
142
|
|
|
141
|
-
const searchParamsSchema =
|
|
143
|
+
const searchParamsSchema = {
|
|
142
144
|
email: z.string().email(),
|
|
143
|
-
}
|
|
145
|
+
};
|
|
144
146
|
|
|
145
147
|
export function MyComponent() {
|
|
146
148
|
const { data } = useSearchParams(searchParamsSchema, {
|
|
@@ -153,7 +155,7 @@ export function MyComponent() {
|
|
|
153
155
|
},
|
|
154
156
|
});
|
|
155
157
|
|
|
156
|
-
if (!data) return <div>
|
|
158
|
+
if (!data) return <div>Error</div>;
|
|
157
159
|
|
|
158
160
|
return <div>Email: {data.email}</div>;
|
|
159
161
|
}
|
|
@@ -183,28 +185,28 @@ const { data } = useSearchParams(schema, options?);
|
|
|
183
185
|
|
|
184
186
|
```tsx
|
|
185
187
|
// ✅ Correct — use coercion for non-string types
|
|
186
|
-
|
|
187
|
-
id: z.string(),
|
|
188
|
-
page: z.coerce.number(),
|
|
189
|
-
active: z.coerce.boolean(),
|
|
190
|
-
count: z.coerce.number().optional(),
|
|
191
|
-
tags: z.array(z.string()),
|
|
192
|
-
}
|
|
188
|
+
{
|
|
189
|
+
id: z.string(), // strings work directly
|
|
190
|
+
page: z.coerce.number(), // coerce string → number
|
|
191
|
+
active: z.coerce.boolean(), // coerce string → boolean
|
|
192
|
+
count: z.coerce.number().optional(), // optional coerced number
|
|
193
|
+
tags: z.array(z.string()), // array of strings (for ?tags=a&tags=b)
|
|
194
|
+
}
|
|
193
195
|
|
|
194
196
|
// ❌ Wrong — will fail because raw input is always a string
|
|
195
|
-
|
|
196
|
-
page: z.number(),
|
|
197
|
+
{
|
|
198
|
+
page: z.number(), // Error: expected number, received string
|
|
197
199
|
active: z.boolean(), // Error: expected boolean, received string
|
|
198
|
-
}
|
|
200
|
+
}
|
|
199
201
|
```
|
|
200
202
|
|
|
201
203
|
You can also use transforms for custom parsing:
|
|
202
204
|
|
|
203
205
|
```tsx
|
|
204
|
-
|
|
206
|
+
{
|
|
205
207
|
date: z.string().transform((s) => new Date(s)),
|
|
206
208
|
ids: z.string().transform((s) => s.split(",")),
|
|
207
|
-
}
|
|
209
|
+
}
|
|
208
210
|
```
|
|
209
211
|
|
|
210
212
|
### JSON-Encoded Objects
|
|
@@ -215,7 +217,7 @@ For complex objects, encode them as JSON in the URL (URL-escaped). Use `.transfo
|
|
|
215
217
|
// URL: ?filter={"category":"books","price":{"min":10,"max":50},"inStock":true}
|
|
216
218
|
// (URL-encoded: ?filter=%7B%22category%22%3A%22books%22...%7D)
|
|
217
219
|
|
|
218
|
-
|
|
220
|
+
{
|
|
219
221
|
filter: z
|
|
220
222
|
.string()
|
|
221
223
|
.transform((s) => JSON.parse(s))
|
|
@@ -227,7 +229,7 @@ z.object({
|
|
|
227
229
|
inStock: z.boolean(),
|
|
228
230
|
})
|
|
229
231
|
),
|
|
230
|
-
}
|
|
232
|
+
}
|
|
231
233
|
```
|
|
232
234
|
|
|
233
235
|
**How it works:**
|
|
@@ -241,7 +243,7 @@ z.object({
|
|
|
241
243
|
```tsx
|
|
242
244
|
export default zPage({
|
|
243
245
|
params: {
|
|
244
|
-
searchParams:
|
|
246
|
+
searchParams: {
|
|
245
247
|
filter: z
|
|
246
248
|
.string()
|
|
247
249
|
.transform((s) => JSON.parse(s))
|
|
@@ -251,7 +253,7 @@ export default zPage({
|
|
|
251
253
|
price: z.object({ min: z.number(), max: z.number() }),
|
|
252
254
|
})
|
|
253
255
|
),
|
|
254
|
-
}
|
|
256
|
+
},
|
|
255
257
|
},
|
|
256
258
|
handler: ({ searchParams }) => {
|
|
257
259
|
// searchParams.filter is fully typed!
|
|
@@ -278,7 +280,7 @@ const url = `/products?filter=${encodeURIComponent(JSON.stringify(filter))}`;
|
|
|
278
280
|
|
|
279
281
|
By default, validation errors redirect to "/404":
|
|
280
282
|
|
|
281
|
-
- **Server components**: `zPage` and `
|
|
283
|
+
- **Server components**: `zPage` and `resolveZPageParams` with `resolve: true` redirects on validation failure
|
|
282
284
|
- **Client components**: `useRouteParams` and `useSearchParams` redirect on validation failure
|
|
283
285
|
|
|
284
286
|
You can customize error handling:
|
|
@@ -286,6 +288,65 @@ You can customize error handling:
|
|
|
286
288
|
- **Server**: Provide an `onError` callback to `zPage` (only available when `resolve: true`)
|
|
287
289
|
- **Client**: Provide an `onError` callback to the hooks
|
|
288
290
|
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Type Utilities
|
|
294
|
+
|
|
295
|
+
The package exports type utilities from `@bb-labs/next-router/common` to help with type inference in advanced scenarios.
|
|
296
|
+
|
|
297
|
+
### `InferZPageParams` / `InferZPageParamsPromises`
|
|
298
|
+
|
|
299
|
+
Infer the resolved param types from a `zPage` configuration. Useful when you need to pass params to child components.
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { zPage } from "@bb-labs/next-router";
|
|
303
|
+
import type { InferZPageParams, InferZPageParamsPromises } from "@bb-labs/next-router/common";
|
|
304
|
+
import { z } from "zod";
|
|
305
|
+
|
|
306
|
+
const pageParams = {
|
|
307
|
+
routeParams: { id: z.string() },
|
|
308
|
+
searchParams: { page: z.coerce.number() },
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Infer the resolved param types
|
|
312
|
+
type PageParams = InferZPageParams<typeof pageParams>;
|
|
313
|
+
// { routeParams: { id: string }, searchParams: { page: number } }
|
|
314
|
+
|
|
315
|
+
// For promise-wrapped params (when resolve: false)
|
|
316
|
+
type PageParamsPromises = InferZPageParamsPromises<typeof pageParams>;
|
|
317
|
+
// { routeParams: Promise<{ id: string }>, searchParams: Promise<{ page: number }> }
|
|
318
|
+
|
|
319
|
+
// Use in child components
|
|
320
|
+
function ProductDetails({ routeParams, searchParams }: PageParams) {
|
|
321
|
+
return (
|
|
322
|
+
<div>
|
|
323
|
+
Product {routeParams.id}, Page {searchParams.page}
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export default zPage({
|
|
329
|
+
params: pageParams,
|
|
330
|
+
handler: (params) => <ProductDetails {...params} />,
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Flexible Schema Input
|
|
335
|
+
|
|
336
|
+
Both server and client APIs accept schemas in two formats:
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
// Format 1: z.object() directly
|
|
340
|
+
z.object({ id: z.string() });
|
|
341
|
+
|
|
342
|
+
// Format 2: Plain object shape (automatically wrapped) — recommended
|
|
343
|
+
{
|
|
344
|
+
id: z.string();
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Both formats are fully type-safe and produce the same runtime behavior.
|
|
349
|
+
|
|
289
350
|
## License
|
|
290
351
|
|
|
291
352
|
MIT
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type
|
|
1
|
+
import type { InferZodObjectShapeOutput, ZodObjectShape } from "../../utils/types";
|
|
2
|
+
import type { OnZodError } from "../../server/z-page/utils/types";
|
|
3
3
|
type HookOptions<T> = {
|
|
4
4
|
onSuccess?: (data: T) => void;
|
|
5
|
-
onError?:
|
|
5
|
+
onError?: OnZodError;
|
|
6
6
|
};
|
|
7
|
-
export declare function useRouteParams<T extends
|
|
8
|
-
data:
|
|
9
|
-
error: null;
|
|
10
|
-
} | {
|
|
11
|
-
data: null;
|
|
12
|
-
error: z.ZodError<z.core.output<T>>;
|
|
7
|
+
export declare function useRouteParams<T extends ZodObjectShape>(schema: T, options?: HookOptions<InferZodObjectShapeOutput<T>>): {
|
|
8
|
+
data: InferZodObjectShapeOutput<T> | null;
|
|
13
9
|
};
|
|
14
|
-
export declare function useSearchParams<T extends
|
|
15
|
-
data:
|
|
16
|
-
error: null;
|
|
17
|
-
} | {
|
|
18
|
-
data: null;
|
|
19
|
-
error: z.ZodError<z.core.output<T>>;
|
|
10
|
+
export declare function useSearchParams<T extends ZodObjectShape>(schema: T, options?: HookOptions<InferZodObjectShapeOutput<T>>): {
|
|
11
|
+
data: InferZodObjectShapeOutput<T> | null;
|
|
20
12
|
};
|
|
21
13
|
export {};
|
|
@@ -1,52 +1,57 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect, useRef, useMemo } from "react";
|
|
3
3
|
import { useParams, useSearchParams as useNextSearchParams, useRouter } from "next/navigation";
|
|
4
|
+
import { toZodObject } from "../../utils/to-zod-object";
|
|
5
|
+
const defaultOnError = (error, router) => {
|
|
6
|
+
console.log(error);
|
|
7
|
+
router.push("/404");
|
|
8
|
+
};
|
|
4
9
|
export function useRouteParams(schema, options) {
|
|
5
10
|
const rawParams = useParams();
|
|
6
11
|
const router = useRouter();
|
|
7
12
|
const calledRef = useRef(false);
|
|
13
|
+
const zodSchema = useMemo(() => toZodObject(schema), [schema]);
|
|
8
14
|
const result = useMemo(() => {
|
|
9
|
-
const parsed =
|
|
15
|
+
const parsed = zodSchema.safeParse(rawParams);
|
|
10
16
|
return parsed.success ? { data: parsed.data, error: null } : { data: null, error: parsed.error };
|
|
11
|
-
}, [rawParams,
|
|
17
|
+
}, [rawParams, zodSchema]);
|
|
12
18
|
useEffect(() => {
|
|
13
19
|
if (calledRef.current)
|
|
14
20
|
return;
|
|
15
21
|
calledRef.current = true;
|
|
16
22
|
if (result.error) {
|
|
17
|
-
|
|
18
|
-
options?.onError ? options.onError(result.error) : router.push("/404");
|
|
23
|
+
options?.onError ? options.onError(result.error) : defaultOnError(result.error, router);
|
|
19
24
|
}
|
|
20
25
|
else if (result.data) {
|
|
21
26
|
options?.onSuccess?.(result.data);
|
|
22
27
|
}
|
|
23
28
|
}, [result, options, router]);
|
|
24
|
-
return result;
|
|
29
|
+
return { data: result.data };
|
|
25
30
|
}
|
|
26
31
|
export function useSearchParams(schema, options) {
|
|
27
32
|
const nextSearchParams = useNextSearchParams();
|
|
28
33
|
const router = useRouter();
|
|
29
34
|
const calledRef = useRef(false);
|
|
35
|
+
const zodSchema = useMemo(() => toZodObject(schema), [schema]);
|
|
30
36
|
const result = useMemo(() => {
|
|
31
37
|
const raw = {};
|
|
32
38
|
nextSearchParams.forEach((v, k) => {
|
|
33
39
|
const existing = raw[k];
|
|
34
40
|
raw[k] = existing ? (Array.isArray(existing) ? [...existing, v] : [existing, v]) : v;
|
|
35
41
|
});
|
|
36
|
-
const parsed =
|
|
42
|
+
const parsed = zodSchema.safeParse(raw);
|
|
37
43
|
return parsed.success ? { data: parsed.data, error: null } : { data: null, error: parsed.error };
|
|
38
|
-
}, [nextSearchParams,
|
|
44
|
+
}, [nextSearchParams, zodSchema]);
|
|
39
45
|
useEffect(() => {
|
|
40
46
|
if (calledRef.current)
|
|
41
47
|
return;
|
|
42
48
|
calledRef.current = true;
|
|
43
49
|
if (result.error) {
|
|
44
|
-
|
|
45
|
-
options?.onError ? options.onError(result.error) : router.push("/404");
|
|
50
|
+
options?.onError ? options.onError(result.error) : defaultOnError(result.error, router);
|
|
46
51
|
}
|
|
47
52
|
else if (result.data) {
|
|
48
53
|
options?.onSuccess?.(result.data);
|
|
49
54
|
}
|
|
50
55
|
}, [result, options, router]);
|
|
51
|
-
return result;
|
|
56
|
+
return { data: result.data };
|
|
52
57
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ZodObjectShape, InferZodObjectShapeOutput } from "../../utils/types";
|
|
2
|
+
/** Helper to infer a single param type */
|
|
3
|
+
type InferParam<T, K extends string> = K extends keyof T ? (T[K] extends ZodObjectShape ? InferZodObjectShapeOutput<T[K]> : never) : never;
|
|
4
|
+
/** Inferred params with Promise wrappers (for async page props) */
|
|
5
|
+
type InferZPageParamsPromises<T> = (T extends {
|
|
6
|
+
routeParams: ZodObjectShape;
|
|
7
|
+
} ? {
|
|
8
|
+
routeParams: Promise<InferParam<T, "routeParams">>;
|
|
9
|
+
} : {}) & (T extends {
|
|
10
|
+
searchParams: ZodObjectShape;
|
|
11
|
+
} ? {
|
|
12
|
+
searchParams: Promise<InferParam<T, "searchParams">>;
|
|
13
|
+
} : {});
|
|
14
|
+
/** Inferred params (unwrapped) */
|
|
15
|
+
type InferZPageParams<T> = (T extends {
|
|
16
|
+
routeParams: ZodObjectShape;
|
|
17
|
+
} ? {
|
|
18
|
+
routeParams: InferParam<T, "routeParams">;
|
|
19
|
+
} : {}) & (T extends {
|
|
20
|
+
searchParams: ZodObjectShape;
|
|
21
|
+
} ? {
|
|
22
|
+
searchParams: InferParam<T, "searchParams">;
|
|
23
|
+
} : {});
|
|
24
|
+
export type { InferZPageParamsPromises, InferZPageParams };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OnZodError } from "../../server/z-page/utils/types";
|
|
2
|
+
type ResolveZPageParamsInput<RP, SP> = {
|
|
3
|
+
routeParams?: Promise<RP>;
|
|
4
|
+
searchParams?: Promise<SP>;
|
|
5
|
+
};
|
|
6
|
+
type ResolveZPageParamsResult<RP, SP> = {
|
|
7
|
+
routeParams: RP;
|
|
8
|
+
searchParams: SP;
|
|
9
|
+
};
|
|
10
|
+
type ResolveZPageParamsOptions = {
|
|
11
|
+
onError?: OnZodError;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveZPageParams<RP, SP>(paramsPromise: ResolveZPageParamsInput<RP, SP>, options?: ResolveZPageParamsOptions): Promise<ResolveZPageParamsResult<RP, SP>>;
|
|
14
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { defaultOnError } from "
|
|
3
|
-
export async function
|
|
2
|
+
import { defaultOnError } from "../../server/z-page/utils/setup";
|
|
3
|
+
export async function resolveZPageParams(paramsPromise, options = {}) {
|
|
4
4
|
const result = {};
|
|
5
5
|
const onError = options.onError ?? defaultOnError;
|
|
6
6
|
if (paramsPromise.routeParams) {
|
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { NextPageProps } from "../types";
|
|
2
|
+
import type { NextPageProps, OnZodError } from "../utils/types";
|
|
3
|
+
import type { InferZodObjectShapeOutput, ZodObjectShape } from "../../../utils/types";
|
|
5
4
|
/** Return type for zPage */
|
|
6
5
|
type ZPageReturn = (props: NextPageProps) => React.ReactNode | Promise<React.ReactNode>;
|
|
7
|
-
|
|
6
|
+
type InferOutput<T extends ZodObjectShape> = InferZodObjectShapeOutput<T>;
|
|
7
|
+
export declare function zPage<RP extends ZodObjectShape, SP extends ZodObjectShape>(cfg: {
|
|
8
8
|
params: {
|
|
9
9
|
routeParams: RP;
|
|
10
10
|
searchParams: SP;
|
|
11
11
|
};
|
|
12
12
|
resolve?: true;
|
|
13
|
-
onError?:
|
|
13
|
+
onError?: OnZodError;
|
|
14
14
|
loadingHandler?: React.ReactNode;
|
|
15
15
|
handler: (input: {
|
|
16
|
-
routeParams:
|
|
17
|
-
searchParams:
|
|
16
|
+
routeParams: InferOutput<RP>;
|
|
17
|
+
searchParams: InferOutput<SP>;
|
|
18
18
|
}) => React.ReactNode | Promise<React.ReactNode>;
|
|
19
19
|
}): ZPageReturn;
|
|
20
|
-
export declare function zPage<RP extends
|
|
20
|
+
export declare function zPage<RP extends ZodObjectShape>(cfg: {
|
|
21
21
|
params: {
|
|
22
22
|
routeParams: RP;
|
|
23
23
|
searchParams?: undefined;
|
|
24
24
|
};
|
|
25
25
|
resolve?: true;
|
|
26
|
-
onError?:
|
|
26
|
+
onError?: OnZodError;
|
|
27
27
|
loadingHandler?: React.ReactNode;
|
|
28
28
|
handler: (input: {
|
|
29
|
-
routeParams:
|
|
29
|
+
routeParams: InferOutput<RP>;
|
|
30
30
|
}) => React.ReactNode | Promise<React.ReactNode>;
|
|
31
31
|
}): ZPageReturn;
|
|
32
|
-
export declare function zPage<SP extends
|
|
32
|
+
export declare function zPage<SP extends ZodObjectShape>(cfg: {
|
|
33
33
|
params: {
|
|
34
34
|
routeParams?: undefined;
|
|
35
35
|
searchParams: SP;
|
|
36
36
|
};
|
|
37
37
|
resolve?: true;
|
|
38
|
-
onError?:
|
|
38
|
+
onError?: OnZodError;
|
|
39
39
|
loadingHandler?: React.ReactNode;
|
|
40
40
|
handler: (input: {
|
|
41
|
-
searchParams:
|
|
41
|
+
searchParams: InferOutput<SP>;
|
|
42
42
|
}) => React.ReactNode | Promise<React.ReactNode>;
|
|
43
43
|
}): ZPageReturn;
|
|
44
|
-
export declare function zPage<RP extends
|
|
44
|
+
export declare function zPage<RP extends ZodObjectShape, SP extends ZodObjectShape>(cfg: {
|
|
45
45
|
params: {
|
|
46
46
|
routeParams: RP;
|
|
47
47
|
searchParams: SP;
|
|
@@ -50,11 +50,11 @@ export declare function zPage<RP extends ZodObject, SP extends ZodObject>(cfg: {
|
|
|
50
50
|
onError?: never;
|
|
51
51
|
loadingHandler?: never;
|
|
52
52
|
handler: (input: {
|
|
53
|
-
routeParams: Promise<
|
|
54
|
-
searchParams: Promise<
|
|
53
|
+
routeParams: Promise<InferOutput<RP>>;
|
|
54
|
+
searchParams: Promise<InferOutput<SP>>;
|
|
55
55
|
}) => React.ReactNode | Promise<React.ReactNode>;
|
|
56
56
|
}): ZPageReturn;
|
|
57
|
-
export declare function zPage<RP extends
|
|
57
|
+
export declare function zPage<RP extends ZodObjectShape>(cfg: {
|
|
58
58
|
params: {
|
|
59
59
|
routeParams: RP;
|
|
60
60
|
searchParams?: undefined;
|
|
@@ -63,10 +63,10 @@ export declare function zPage<RP extends ZodObject>(cfg: {
|
|
|
63
63
|
onError?: never;
|
|
64
64
|
loadingHandler?: never;
|
|
65
65
|
handler: (input: {
|
|
66
|
-
routeParams: Promise<
|
|
66
|
+
routeParams: Promise<InferOutput<RP>>;
|
|
67
67
|
}) => React.ReactNode | Promise<React.ReactNode>;
|
|
68
68
|
}): ZPageReturn;
|
|
69
|
-
export declare function zPage<SP extends
|
|
69
|
+
export declare function zPage<SP extends ZodObjectShape>(cfg: {
|
|
70
70
|
params: {
|
|
71
71
|
routeParams?: undefined;
|
|
72
72
|
searchParams: SP;
|
|
@@ -75,7 +75,7 @@ export declare function zPage<SP extends ZodObject>(cfg: {
|
|
|
75
75
|
onError?: never;
|
|
76
76
|
loadingHandler?: never;
|
|
77
77
|
handler: (input: {
|
|
78
|
-
searchParams: Promise<
|
|
78
|
+
searchParams: Promise<InferOutput<SP>>;
|
|
79
79
|
}) => React.ReactNode | Promise<React.ReactNode>;
|
|
80
80
|
}): ZPageReturn;
|
|
81
81
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Suspense } from "react";
|
|
3
|
-
import { setup } from "../utils/
|
|
3
|
+
import { setup } from "../utils/setup";
|
|
4
4
|
// ------------- IMPLEMENTATION -------------
|
|
5
5
|
export function zPage(cfg) {
|
|
6
6
|
const shouldResolve = cfg.resolve !== false;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NextPageProps, OnZodError } from "./types";
|
|
2
|
+
import type { ZodObjectShape } from "../../../utils/types";
|
|
3
|
+
export declare const defaultOnError: OnZodError;
|
|
4
|
+
export declare function setup<RP extends ZodObjectShape, SP extends ZodObjectShape>(params: {
|
|
5
|
+
routeParams?: RP;
|
|
6
|
+
searchParams?: SP;
|
|
7
|
+
}, onError?: OnZodError): {
|
|
8
|
+
createValidatedPromises: (props: NextPageProps) => {
|
|
9
|
+
routeParams: Promise<Record<string, unknown>> | undefined;
|
|
10
|
+
searchParams: Promise<Record<string, unknown>> | undefined;
|
|
11
|
+
};
|
|
12
|
+
resolveParams: (promises: {
|
|
13
|
+
routeParams: Promise<Record<string, unknown>> | undefined;
|
|
14
|
+
searchParams: Promise<Record<string, unknown>> | undefined;
|
|
15
|
+
}) => Promise<Record<string, unknown>>;
|
|
16
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { redirect } from "next/navigation";
|
|
3
|
+
import { toZodObject } from "../../../utils/to-zod-object";
|
|
3
4
|
export const defaultOnError = (e) => {
|
|
4
5
|
console.log(e);
|
|
5
6
|
redirect("/404");
|
|
6
7
|
};
|
|
7
8
|
export function setup(params, onError = defaultOnError) {
|
|
8
|
-
const rpSchema = params.routeParams;
|
|
9
|
-
const spSchema = params.searchParams;
|
|
9
|
+
const rpSchema = params.routeParams ? toZodObject(params.routeParams) : undefined;
|
|
10
|
+
const spSchema = params.searchParams ? toZodObject(params.searchParams) : undefined;
|
|
10
11
|
/** Creates validated promises from Next.js page props */
|
|
11
12
|
function createValidatedPromises(props) {
|
|
12
13
|
return {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { z
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
/** Next.js page props type */
|
|
3
3
|
export type NextPageProps = {
|
|
4
4
|
params: Promise<Record<string, string | string[]>>;
|
|
5
5
|
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
|
6
6
|
};
|
|
7
|
-
/** Enforces that routeParams/searchParams must be z.object(...) */
|
|
8
|
-
export type ZodObject = z.ZodObject<ZodRawShape>;
|
|
9
7
|
/** Error handler type */
|
|
10
|
-
export type
|
|
8
|
+
export type OnZodError = (error: z.ZodError) => never | void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z, type ZodRawShape } from "zod";
|
|
2
|
+
/** A z.object(...) schema */
|
|
3
|
+
export type ZodObject<T extends ZodRawShape = ZodRawShape> = z.ZodObject<T>;
|
|
4
|
+
/** Plain object shape or z.object() - accepts either format */
|
|
5
|
+
export type ZodObjectShape = ZodRawShape | ZodObject;
|
|
6
|
+
/** Infer output type from either raw shape or ZodObject */
|
|
7
|
+
export type InferZodObjectShapeOutput<T extends ZodObjectShape> = T extends ZodObject<infer S> ? z.output<z.ZodObject<S>> : T extends ZodRawShape ? z.output<z.ZodObject<T>> : never;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bb-labs/next-router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"author": "Beepbop",
|
|
5
5
|
"homepage": "https://github.com/beepbop-labs/next-router",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
],
|
|
22
22
|
"exports": {
|
|
23
23
|
".": "./dist/server/index.js",
|
|
24
|
-
"./client": "./dist/client/index.js"
|
|
24
|
+
"./client": "./dist/client/index.js",
|
|
25
|
+
"./common": "./dist/common/index.js"
|
|
25
26
|
},
|
|
26
27
|
"scripts": {
|
|
27
28
|
"clean": "rm -rf dist",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { z, type ZodObject, type ZodRawShape } from "zod";
|
|
2
|
-
type InferParamsPromises<T> = ("routeParams" extends keyof T ? (T["routeParams"] extends ZodObject<ZodRawShape> ? {
|
|
3
|
-
routeParams: Promise<z.output<T["routeParams"]>>;
|
|
4
|
-
} : {}) : {}) & ("searchParams" extends keyof T ? (T["searchParams"] extends ZodObject<ZodRawShape> ? {
|
|
5
|
-
searchParams: Promise<z.output<T["searchParams"]>>;
|
|
6
|
-
} : {}) : {});
|
|
7
|
-
type InferParams<T> = ("routeParams" extends keyof T ? (T["routeParams"] extends ZodObject<ZodRawShape> ? {
|
|
8
|
-
routeParams: z.output<T["routeParams"]>;
|
|
9
|
-
} : {}) : {}) & ("searchParams" extends keyof T ? (T["searchParams"] extends ZodObject<ZodRawShape> ? {
|
|
10
|
-
searchParams: z.output<T["searchParams"]>;
|
|
11
|
-
} : {}) : {});
|
|
12
|
-
export type { InferParamsPromises, InferParams };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type OnError } from "../utils/helper";
|
|
2
|
-
type ResolveParamsInput<RP, SP> = {
|
|
3
|
-
routeParams?: Promise<RP>;
|
|
4
|
-
searchParams?: Promise<SP>;
|
|
5
|
-
};
|
|
6
|
-
type ResolveParamsResult<RP, SP> = {
|
|
7
|
-
routeParams: RP;
|
|
8
|
-
searchParams: SP;
|
|
9
|
-
};
|
|
10
|
-
type ResolveParamsOptions = {
|
|
11
|
-
onError?: OnError;
|
|
12
|
-
};
|
|
13
|
-
export declare function resolveParams<RP, SP>(paramsPromise: ResolveParamsInput<RP, SP>, options?: ResolveParamsOptions): Promise<ResolveParamsResult<RP, SP>>;
|
|
14
|
-
export {};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { NextPageProps, ZodObject, OnError } from "../types";
|
|
3
|
-
export type { ZodObject, OnError };
|
|
4
|
-
export declare const defaultOnError: OnError;
|
|
5
|
-
export declare function setup<RP extends ZodObject, SP extends ZodObject>(params: {
|
|
6
|
-
routeParams?: RP;
|
|
7
|
-
searchParams?: SP;
|
|
8
|
-
}, onError?: OnError): {
|
|
9
|
-
createValidatedPromises: (props: NextPageProps) => {
|
|
10
|
-
routeParams: Promise<z.core.output<RP>> | undefined;
|
|
11
|
-
searchParams: Promise<z.core.output<SP>> | undefined;
|
|
12
|
-
};
|
|
13
|
-
resolveParams: (promises: {
|
|
14
|
-
routeParams: Promise<z.core.output<RP>> | undefined;
|
|
15
|
-
searchParams: Promise<z.core.output<SP>> | undefined;
|
|
16
|
-
}) => Promise<Record<string, unknown>>;
|
|
17
|
-
};
|
|
File without changes
|
|
File without changes
|