@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 CHANGED
@@ -35,9 +35,9 @@ import { z } from "zod";
35
35
 
36
36
  export default zPage({
37
37
  params: {
38
- searchParams: z.object({
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, resolveParams } from "@bb-labs/next-router";
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: z.object({ id: z.string() }),
65
- searchParams: z.object({ page: z.coerce.number().optional() }),
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 resolveParams(paramsPromise);
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
- ### `resolveParams`
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, resolveParams } from "@bb-labs/next-router";
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: z.object({
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 resolveParams(paramsPromise, {
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: z.object({...}), searchParams: z.object({...}) } }
117
+ { params: { routeParams: {...}, searchParams: {...} } }
116
118
 
117
119
  // ✅ Only routeParams
118
- { params: { routeParams: z.object({...}) } }
120
+ { params: { routeParams: {...} } }
119
121
 
120
122
  // ✅ Only searchParams
121
- { params: { searchParams: z.object({...}) } }
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 = z.object({
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 (!data) return <div>Error</div>;
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: Error) => void` — Called when validation fails (defaults to redirecting to "/404")
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
- z.object({
187
- id: z.string(), // strings work directly
188
- page: z.coerce.number(), // coerce string → number
189
- active: z.coerce.boolean(), // coerce string → boolean
190
- count: z.coerce.number().optional(), // optional coerced number
191
- tags: z.array(z.string()), // array of strings (for ?tags=a&tags=b)
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
- z.object({
196
- page: z.number(), // Error: expected number, received string
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
- z.object({
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
- z.object({
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: z.object({
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 `resolveParams` with `resolve: true` redirects on validation failure
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 { z, type ZodRawShape } from "zod";
2
- type ZodObject = z.ZodObject<ZodRawShape>;
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?: (error: Error) => void;
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 = schema.safeParse(rawParams);
15
+ const parsed = zodSchema.safeParse(rawParams);
10
16
  return parsed.success ? { data: parsed.data, error: null } : { data: null, error: parsed.error };
11
- }, [rawParams, schema]);
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
- console.log(result.error);
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 = schema.safeParse(raw);
42
+ const parsed = zodSchema.safeParse(raw);
37
43
  return parsed.success ? { data: parsed.data, error: null } : { data: null, error: parsed.error };
38
- }, [nextSearchParams, schema]);
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
- console.log(result.error);
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,2 @@
1
+ export * from "./z-page/infer-params";
2
+ export * from "./z-page/resolve-params";
@@ -0,0 +1,2 @@
1
+ export * from "./z-page/infer-params";
2
+ export * from "./z-page/resolve-params";
@@ -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 "../utils/helper";
3
- export async function resolveParams(paramsPromise, options = {}) {
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 { z } from "zod";
3
- import { type ZodObject, type OnError } from "../utils/helper";
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
- export declare function zPage<RP extends ZodObject, SP extends ZodObject>(cfg: {
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?: OnError;
13
+ onError?: OnZodError;
14
14
  loadingHandler?: React.ReactNode;
15
15
  handler: (input: {
16
- routeParams: z.output<RP>;
17
- searchParams: z.output<SP>;
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 ZodObject>(cfg: {
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?: OnError;
26
+ onError?: OnZodError;
27
27
  loadingHandler?: React.ReactNode;
28
28
  handler: (input: {
29
- routeParams: z.output<RP>;
29
+ routeParams: InferOutput<RP>;
30
30
  }) => React.ReactNode | Promise<React.ReactNode>;
31
31
  }): ZPageReturn;
32
- export declare function zPage<SP extends ZodObject>(cfg: {
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?: OnError;
38
+ onError?: OnZodError;
39
39
  loadingHandler?: React.ReactNode;
40
40
  handler: (input: {
41
- searchParams: z.output<SP>;
41
+ searchParams: InferOutput<SP>;
42
42
  }) => React.ReactNode | Promise<React.ReactNode>;
43
43
  }): ZPageReturn;
44
- export declare function zPage<RP extends ZodObject, SP extends ZodObject>(cfg: {
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<z.output<RP>>;
54
- searchParams: Promise<z.output<SP>>;
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 ZodObject>(cfg: {
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<z.output<RP>>;
66
+ routeParams: Promise<InferOutput<RP>>;
67
67
  }) => React.ReactNode | Promise<React.ReactNode>;
68
68
  }): ZPageReturn;
69
- export declare function zPage<SP extends ZodObject>(cfg: {
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<z.output<SP>>;
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/helper";
3
+ import { setup } from "../utils/setup";
4
4
  // ------------- IMPLEMENTATION -------------
5
5
  export function zPage(cfg) {
6
6
  const shouldResolve = cfg.resolve !== false;
@@ -1,3 +1 @@
1
1
  export { zPage } from "./fns/z-page";
2
- export { resolveParams } from "./helpers/resolve-params";
3
- export type { InferParamsPromises, InferParams } from "./helpers/infer-props";
@@ -1,2 +1 @@
1
1
  export { zPage } from "./fns/z-page";
2
- export { resolveParams } from "./helpers/resolve-params";
@@ -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, type ZodRawShape } from "zod";
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 OnError = (error: z.ZodError) => never | void;
8
+ export type OnZodError = (error: z.ZodError) => never | void;
@@ -0,0 +1,4 @@
1
+ import { z, type ZodRawShape } from "zod";
2
+ import type { ZodObjectShape } from "./types";
3
+ /** Wrap a raw shape in z.object() if it's not already a ZodObject */
4
+ export declare function toZodObject(schema: ZodObjectShape): z.ZodObject<ZodRawShape>;
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ /** Wrap a raw shape in z.object() if it's not already a ZodObject */
3
+ export function toZodObject(schema) {
4
+ if (schema instanceof z.ZodObject)
5
+ return schema;
6
+ return z.object(schema);
7
+ }
@@ -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.2",
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