@bb-labs/next-router 0.0.2 → 0.0.4
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 +100 -38
- package/dist/client/z-page/index.d.ts +5 -9
- 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 +14 -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,12 +140,12 @@ 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
|
-
const { data } = useSearchParams(searchParamsSchema, {
|
|
148
|
+
const { data, error } = useSearchParams(searchParamsSchema, {
|
|
147
149
|
onSuccess: (data) => {
|
|
148
150
|
console.log("Valid params:", data.email);
|
|
149
151
|
},
|
|
@@ -153,7 +155,7 @@ export function MyComponent() {
|
|
|
153
155
|
},
|
|
154
156
|
});
|
|
155
157
|
|
|
156
|
-
if (
|
|
158
|
+
if (error) return <div>Error: {error}</div>;
|
|
157
159
|
|
|
158
160
|
return <div>Email: {data.email}</div>;
|
|
159
161
|
}
|
|
@@ -162,18 +164,19 @@ export function MyComponent() {
|
|
|
162
164
|
### Hook API
|
|
163
165
|
|
|
164
166
|
```tsx
|
|
165
|
-
const { data } = useRouteParams(schema, options?);
|
|
166
|
-
const { data } = useSearchParams(schema, options?);
|
|
167
|
+
const { data, error } = useRouteParams(schema, options?);
|
|
168
|
+
const { data, error } = useSearchParams(schema, options?);
|
|
167
169
|
```
|
|
168
170
|
|
|
169
171
|
**Options:**
|
|
170
172
|
|
|
171
173
|
- `onSuccess?: (data: T) => void` — Called when params are successfully validated
|
|
172
|
-
- `onError?: (error:
|
|
174
|
+
- `onError?: (error: z.ZodError) => void` — Called when validation fails (defaults to redirecting to "/404")
|
|
173
175
|
|
|
174
176
|
**Returns:**
|
|
175
177
|
|
|
176
178
|
- `data` — The validated params, or `null` if validation failed
|
|
179
|
+
- `error` — The error message string, or `null` if validation succeeded
|
|
177
180
|
|
|
178
181
|
---
|
|
179
182
|
|
|
@@ -183,28 +186,28 @@ const { data } = useSearchParams(schema, options?);
|
|
|
183
186
|
|
|
184
187
|
```tsx
|
|
185
188
|
// ✅ 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
|
-
}
|
|
189
|
+
{
|
|
190
|
+
id: z.string(), // strings work directly
|
|
191
|
+
page: z.coerce.number(), // coerce string → number
|
|
192
|
+
active: z.coerce.boolean(), // coerce string → boolean
|
|
193
|
+
count: z.coerce.number().optional(), // optional coerced number
|
|
194
|
+
tags: z.array(z.string()), // array of strings (for ?tags=a&tags=b)
|
|
195
|
+
}
|
|
193
196
|
|
|
194
197
|
// ❌ Wrong — will fail because raw input is always a string
|
|
195
|
-
|
|
196
|
-
page: z.number(),
|
|
198
|
+
{
|
|
199
|
+
page: z.number(), // Error: expected number, received string
|
|
197
200
|
active: z.boolean(), // Error: expected boolean, received string
|
|
198
|
-
}
|
|
201
|
+
}
|
|
199
202
|
```
|
|
200
203
|
|
|
201
204
|
You can also use transforms for custom parsing:
|
|
202
205
|
|
|
203
206
|
```tsx
|
|
204
|
-
|
|
207
|
+
{
|
|
205
208
|
date: z.string().transform((s) => new Date(s)),
|
|
206
209
|
ids: z.string().transform((s) => s.split(",")),
|
|
207
|
-
}
|
|
210
|
+
}
|
|
208
211
|
```
|
|
209
212
|
|
|
210
213
|
### JSON-Encoded Objects
|
|
@@ -215,7 +218,7 @@ For complex objects, encode them as JSON in the URL (URL-escaped). Use `.transfo
|
|
|
215
218
|
// URL: ?filter={"category":"books","price":{"min":10,"max":50},"inStock":true}
|
|
216
219
|
// (URL-encoded: ?filter=%7B%22category%22%3A%22books%22...%7D)
|
|
217
220
|
|
|
218
|
-
|
|
221
|
+
{
|
|
219
222
|
filter: z
|
|
220
223
|
.string()
|
|
221
224
|
.transform((s) => JSON.parse(s))
|
|
@@ -227,7 +230,7 @@ z.object({
|
|
|
227
230
|
inStock: z.boolean(),
|
|
228
231
|
})
|
|
229
232
|
),
|
|
230
|
-
}
|
|
233
|
+
}
|
|
231
234
|
```
|
|
232
235
|
|
|
233
236
|
**How it works:**
|
|
@@ -241,7 +244,7 @@ z.object({
|
|
|
241
244
|
```tsx
|
|
242
245
|
export default zPage({
|
|
243
246
|
params: {
|
|
244
|
-
searchParams:
|
|
247
|
+
searchParams: {
|
|
245
248
|
filter: z
|
|
246
249
|
.string()
|
|
247
250
|
.transform((s) => JSON.parse(s))
|
|
@@ -251,7 +254,7 @@ export default zPage({
|
|
|
251
254
|
price: z.object({ min: z.number(), max: z.number() }),
|
|
252
255
|
})
|
|
253
256
|
),
|
|
254
|
-
}
|
|
257
|
+
},
|
|
255
258
|
},
|
|
256
259
|
handler: ({ searchParams }) => {
|
|
257
260
|
// searchParams.filter is fully typed!
|
|
@@ -278,7 +281,7 @@ const url = `/products?filter=${encodeURIComponent(JSON.stringify(filter))}`;
|
|
|
278
281
|
|
|
279
282
|
By default, validation errors redirect to "/404":
|
|
280
283
|
|
|
281
|
-
- **Server components**: `zPage` and `
|
|
284
|
+
- **Server components**: `zPage` and `resolveZPageParams` with `resolve: true` redirects on validation failure
|
|
282
285
|
- **Client components**: `useRouteParams` and `useSearchParams` redirect on validation failure
|
|
283
286
|
|
|
284
287
|
You can customize error handling:
|
|
@@ -286,6 +289,65 @@ You can customize error handling:
|
|
|
286
289
|
- **Server**: Provide an `onError` callback to `zPage` (only available when `resolve: true`)
|
|
287
290
|
- **Client**: Provide an `onError` callback to the hooks
|
|
288
291
|
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Type Utilities
|
|
295
|
+
|
|
296
|
+
The package exports type utilities from `@bb-labs/next-router/common` to help with type inference in advanced scenarios.
|
|
297
|
+
|
|
298
|
+
### `InferZPageParams` / `InferZPageParamsPromises`
|
|
299
|
+
|
|
300
|
+
Infer the resolved param types from a `zPage` configuration. Useful when you need to pass params to child components.
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import { zPage } from "@bb-labs/next-router";
|
|
304
|
+
import type { InferZPageParams, InferZPageParamsPromises } from "@bb-labs/next-router/common";
|
|
305
|
+
import { z } from "zod";
|
|
306
|
+
|
|
307
|
+
const pageParams = {
|
|
308
|
+
routeParams: { id: z.string() },
|
|
309
|
+
searchParams: { page: z.coerce.number() },
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Infer the resolved param types
|
|
313
|
+
type PageParams = InferZPageParams<typeof pageParams>;
|
|
314
|
+
// { routeParams: { id: string }, searchParams: { page: number } }
|
|
315
|
+
|
|
316
|
+
// For promise-wrapped params (when resolve: false)
|
|
317
|
+
type PageParamsPromises = InferZPageParamsPromises<typeof pageParams>;
|
|
318
|
+
// { routeParams: Promise<{ id: string }>, searchParams: Promise<{ page: number }> }
|
|
319
|
+
|
|
320
|
+
// Use in child components
|
|
321
|
+
function ProductDetails({ routeParams, searchParams }: PageParams) {
|
|
322
|
+
return (
|
|
323
|
+
<div>
|
|
324
|
+
Product {routeParams.id}, Page {searchParams.page}
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export default zPage({
|
|
330
|
+
params: pageParams,
|
|
331
|
+
handler: (params) => <ProductDetails {...params} />,
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Flexible Schema Input
|
|
336
|
+
|
|
337
|
+
Both server and client APIs accept schemas in two formats:
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
// Format 1: z.object() directly
|
|
341
|
+
z.object({ id: z.string() });
|
|
342
|
+
|
|
343
|
+
// Format 2: Plain object shape (automatically wrapped) — recommended
|
|
344
|
+
{
|
|
345
|
+
id: z.string();
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Both formats are fully type-safe and produce the same runtime behavior.
|
|
350
|
+
|
|
289
351
|
## License
|
|
290
352
|
|
|
291
353
|
MIT
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type
|
|
1
|
+
import type { InferZodObjectShapeOutput, T_DataError, 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?:
|
|
6
|
-
};
|
|
7
|
-
export declare function useRouteParams<T extends ZodObject>(schema: T, options?: HookOptions<z.output<T>>): {
|
|
8
|
-
data: z.core.output<T> | null;
|
|
9
|
-
};
|
|
10
|
-
export declare function useSearchParams<T extends ZodObject>(schema: T, options?: HookOptions<z.output<T>>): {
|
|
11
|
-
data: z.core.output<T> | null;
|
|
5
|
+
onError?: OnZodError;
|
|
12
6
|
};
|
|
7
|
+
export declare function useRouteParams<T extends ZodObjectShape>(schema: T, options?: HookOptions<InferZodObjectShapeOutput<T>>): T_DataError<InferZodObjectShapeOutput<T>>;
|
|
8
|
+
export declare function useSearchParams<T extends ZodObjectShape>(schema: T, options?: HookOptions<InferZodObjectShapeOutput<T>>): T_DataError<InferZodObjectShapeOutput<T>>;
|
|
13
9
|
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 { data: result.data };
|
|
29
|
+
return result.error ? { data: null, error: result.error.message } : { data: result.data, error: null };
|
|
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 { data: result.data };
|
|
56
|
+
return result.error ? { data: null, error: result.error.message } : { data: result.data, error: null };
|
|
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,14 @@
|
|
|
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;
|
|
8
|
+
export type T_DataError<T> = {
|
|
9
|
+
data: T;
|
|
10
|
+
error: null;
|
|
11
|
+
} | {
|
|
12
|
+
data: null;
|
|
13
|
+
error: string;
|
|
14
|
+
};
|
|
@@ -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.4",
|
|
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
|