@zubyjs/next 1.0.83

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 ADDED
@@ -0,0 +1,446 @@
1
+ # @zubyjs/next
2
+
3
+ The plugin for Zuby.js that adds compatibility for existing Next.js projects with the pages router. It automatically converts Next.js pages and API routes to Zuby.js handlers, providing a smooth migration path.
4
+
5
+ ## Features
6
+
7
+ - **Automatic API Route Detection**: Scans `./pages/api` directory and converts Next.js API routes to Zuby handlers
8
+ - **Next.js API Compatibility**: Provides `NextApiRequest` and `NextApiResponse` objects compatible with Next.js conventions
9
+ - **HTTP Method Support**: Supports method-specific handlers (`get`, `post`, `put`, `delete`, `patch`, `head`, `options`)
10
+ - **Default Exports**: Works with both default exports (all methods) and method-specific exports
11
+ - **Dynamic Routes**: Automatically converts dynamic routes like `[id]` to `:id` format
12
+ - **Nested Routes**: Supports nested API routes with proper path handling
13
+ - **Error Handling**: Built-in error handling and logging
14
+
15
+ ## Installation
16
+
17
+ First, install the `@zubyjs/next` package using your favorite package manager.
18
+ If you aren't sure, you can use npm:
19
+
20
+ ```sh
21
+ npm install @zubyjs/next
22
+ ```
23
+
24
+ Then add the `@zubyjs/next` plugin to your `zuby.config.mjs` file under the `plugins` option:
25
+
26
+ ```javascript
27
+ import { defineConfig } from 'zuby';
28
+ import preact from '@zubyjs/preact';
29
+ import next from '@zubyjs/next';
30
+
31
+ export default defineConfig({
32
+ outDir: '.zuby',
33
+ jsx: preact(),
34
+ plugins: [
35
+ next()
36
+ ]
37
+ });
38
+ ```
39
+
40
+ ## Configuration
41
+
42
+ ### Options
43
+
44
+ The plugin accepts the following options:
45
+
46
+ ```typescript
47
+ interface NextPluginOptions {
48
+ /**
49
+ * Whether to automatically detect and convert Next.js pages/api routes
50
+ * @default true
51
+ */
52
+ autoDetectApiRoutes?: boolean;
53
+
54
+ /**
55
+ * Custom pages directory relative to srcDir
56
+ * @default ./pages
57
+ */
58
+ pagesDir?: string;
59
+ }
60
+ ```
61
+
62
+ ### Usage with Options
63
+
64
+ ```javascript
65
+ import next from '@zubyjs/next';
66
+
67
+ export default defineConfig({
68
+ plugins: [
69
+ next({
70
+ autoDetectApiRoutes: true,
71
+ pagesDir: './pages'
72
+ })
73
+ ]
74
+ });
75
+ ```
76
+
77
+ ## API Routes
78
+
79
+ Next.js API routes in `./pages/api` are automatically converted to Zuby handlers. The plugin supports:
80
+
81
+ ### 1. Default Export Handler (All Methods)
82
+
83
+ ```typescript
84
+ // pages/api/hello.ts
85
+ export default function handler(req: NextApiRequest, res: NextApiResponse) {
86
+ return new Response('Hello from Zuby!', { status: 200 });
87
+ }
88
+ ```
89
+
90
+ ### 2. HTTP Method-Specific Handlers
91
+
92
+ ```typescript
93
+ // pages/api/posts.ts
94
+ export async function get(req: NextApiRequest, res: NextApiResponse) {
95
+ const posts = await fetchPosts();
96
+ return new Response(JSON.stringify(posts), {
97
+ status: 200,
98
+ headers: { 'content-type': 'application/json' }
99
+ });
100
+ }
101
+
102
+ export async function post(req: NextApiRequest, res: NextApiResponse) {
103
+ const newPost = await createPost(req.body);
104
+ return new Response(JSON.stringify(newPost), {
105
+ status: 201,
106
+ headers: { 'content-type': 'application/json' }
107
+ });
108
+ }
109
+
110
+ export async function put(req: NextApiRequest, res: NextApiResponse) {
111
+ const updatedPost = await updatePost(req.body);
112
+ return new Response(JSON.stringify(updatedPost), { status: 200 });
113
+ }
114
+
115
+ export async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
116
+ await deletePost(req.query.id);
117
+ return new Response(null, { status: 204 });
118
+ }
119
+ ```
120
+
121
+ ### 3. Dynamic Routes
122
+
123
+ Dynamic route segments are automatically converted:
124
+
125
+ ```typescript
126
+ // pages/api/posts/[id].ts
127
+ export async function get(req: NextApiRequest, res: NextApiResponse) {
128
+ const { id } = req.query;
129
+ const post = await fetchPost(id);
130
+
131
+ if (!post) {
132
+ return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 });
133
+ }
134
+
135
+ return new Response(JSON.stringify(post), { status: 200 });
136
+ }
137
+ ```
138
+
139
+ The route `pages/api/posts/[id].ts` becomes `/posts/:id`
140
+
141
+ ### 4. Nested Dynamic Routes
142
+
143
+ ```typescript
144
+ // pages/api/[tenant]/posts/[id].ts
145
+ export async function get(req: NextApiRequest, res: NextApiResponse) {
146
+ const { tenant, id } = req.query;
147
+ const post = await fetchPost(tenant, id);
148
+ return new Response(JSON.stringify(post), { status: 200 });
149
+ }
150
+ ```
151
+
152
+ The route becomes `/:tenant/posts/:id`
153
+
154
+ ## NextApiRequest Object
155
+
156
+ The `NextApiRequest` object provides the following properties:
157
+
158
+ ```typescript
159
+ interface NextApiRequest {
160
+ // The request URL
161
+ url?: string;
162
+
163
+ // The HTTP method (GET, POST, PUT, DELETE, etc.)
164
+ method?: string;
165
+
166
+ // Query parameters from URL and path params
167
+ query: Record<string, string | string[]>;
168
+
169
+ // Request cookies
170
+ cookies: Record<string, string>;
171
+
172
+ // Request headers
173
+ headers: Record<string, string | string[] | undefined>;
174
+
175
+ // Request body (for POST/PUT requests)
176
+ body?: any;
177
+
178
+ // Base URL
179
+ baseUrl?: string;
180
+
181
+ // Path without query string
182
+ pathname?: string;
183
+
184
+ // HTTP version
185
+ httpVersion?: string;
186
+
187
+ // Socket information with client IP
188
+ socket?: { remoteAddress?: string };
189
+ }
190
+ ```
191
+
192
+ ## NextApiResponse Object
193
+
194
+ The `NextApiResponse` object provides the following methods and properties:
195
+
196
+ ```typescript
197
+ interface NextApiResponse {
198
+ // HTTP status code
199
+ statusCode: number;
200
+
201
+ // Response headers
202
+ headers: Record<string, string | string[] | number>;
203
+
204
+ // Set the status code (chainable)
205
+ status(code: number): this;
206
+
207
+ // Set a response header (chainable)
208
+ setHeader(name: string, value: string | string[] | number): this;
209
+
210
+ // Get a response header
211
+ getHeader(name: string): string | string[] | number | undefined;
212
+
213
+ // Check if headers have been sent
214
+ headersSent: boolean;
215
+
216
+ // Send JSON response
217
+ json(body: any): void;
218
+
219
+ // Send a response
220
+ send(body: any): void;
221
+
222
+ // End the response
223
+ end(body?: any): void;
224
+
225
+ // Write data to response
226
+ write(chunk: string | Buffer): void;
227
+
228
+ // Redirect to a URL
229
+ redirect(statusOrUrl: string | number, url?: string): void;
230
+ }
231
+ ```
232
+
233
+ ## Examples
234
+
235
+ ### Basic JSON API
236
+
237
+ ```typescript
238
+ // pages/api/users.ts
239
+ export async function get(req, res) {
240
+ const users = [
241
+ { id: 1, name: 'Alice' },
242
+ { id: 2, name: 'Bob' }
243
+ ];
244
+
245
+ return new Response(JSON.stringify(users), {
246
+ status: 200,
247
+ headers: { 'content-type': 'application/json' }
248
+ });
249
+ }
250
+
251
+ export async function post(req, res) {
252
+ const user = req.body;
253
+ const newUser = await saveUser(user);
254
+
255
+ return new Response(JSON.stringify(newUser), {
256
+ status: 201,
257
+ headers: { 'content-type': 'application/json' }
258
+ });
259
+ }
260
+ ```
261
+
262
+ ### Query Parameters
263
+
264
+ ```typescript
265
+ // pages/api/search.ts
266
+ export async function get(req, res) {
267
+ const { q, limit = '10' } = req.query;
268
+
269
+ if (!q) {
270
+ return new Response(JSON.stringify({ error: 'Missing query parameter' }), {
271
+ status: 400,
272
+ headers: { 'content-type': 'application/json' }
273
+ });
274
+ }
275
+
276
+ const results = await search(String(q), parseInt(String(limit)));
277
+
278
+ return new Response(JSON.stringify(results), {
279
+ status: 200,
280
+ headers: { 'content-type': 'application/json' }
281
+ });
282
+ }
283
+ ```
284
+
285
+ ### Dynamic Routes with Params
286
+
287
+ ```typescript
288
+ // pages/api/posts/[id]/comments.ts
289
+ export async function get(req, res) {
290
+ const { id } = req.query;
291
+ const comments = await fetchComments(id);
292
+
293
+ return new Response(JSON.stringify(comments), {
294
+ status: 200,
295
+ headers: { 'content-type': 'application/json' }
296
+ });
297
+ }
298
+ ```
299
+
300
+ ### Request Body
301
+
302
+ ```typescript
303
+ // pages/api/create-user.ts
304
+ export async function post(req, res) {
305
+ const { name, email } = req.body;
306
+
307
+ // Validate input
308
+ if (!name || !email) {
309
+ return new Response(JSON.stringify({ error: 'Missing fields' }), {
310
+ status: 400,
311
+ headers: { 'content-type': 'application/json' }
312
+ });
313
+ }
314
+
315
+ const user = await createUser({ name, email });
316
+
317
+ res.status(201);
318
+ return new Response(JSON.stringify(user), {
319
+ status: 201,
320
+ headers: { 'content-type': 'application/json' }
321
+ });
322
+ }
323
+ ```
324
+
325
+ ### Cookies
326
+
327
+ ```typescript
328
+ // pages/api/login.ts
329
+ export async function post(req, res) {
330
+ const { username, password } = req.body;
331
+
332
+ const authenticated = await authenticate(username, password);
333
+
334
+ if (authenticated) {
335
+ const sessionId = generateSessionId();
336
+
337
+ res.setHeader('set-cookie', `session=${sessionId}; Path=/; HttpOnly`);
338
+
339
+ return new Response(JSON.stringify({ success: true }), {
340
+ status: 200,
341
+ headers: { 'content-type': 'application/json' }
342
+ });
343
+ }
344
+
345
+ return new Response(JSON.stringify({ error: 'Invalid credentials' }), {
346
+ status: 401,
347
+ headers: { 'content-type': 'application/json' }
348
+ });
349
+ }
350
+ ```
351
+
352
+ ### Error Handling
353
+
354
+ ```typescript
355
+ // pages/api/data.ts
356
+ export async function get(req, res) {
357
+ try {
358
+ const data = await fetchData();
359
+ return new Response(JSON.stringify(data), { status: 200 });
360
+ } catch (error) {
361
+ console.error('Error fetching data:', error);
362
+
363
+ return new Response(
364
+ JSON.stringify({
365
+ error: 'Internal server error',
366
+ message: error instanceof Error ? error.message : 'Unknown error'
367
+ }),
368
+ {
369
+ status: 500,
370
+ headers: { 'content-type': 'application/json' }
371
+ }
372
+ );
373
+ }
374
+ }
375
+ ```
376
+
377
+ ### Custom Headers
378
+
379
+ ```typescript
380
+ // pages/api/download.ts
381
+ export async function get(req, res) {
382
+ const data = await generateReport();
383
+
384
+ return new Response(data, {
385
+ status: 200,
386
+ headers: {
387
+ 'content-type': 'application/pdf',
388
+ 'content-disposition': 'attachment; filename="report.pdf"'
389
+ }
390
+ });
391
+ }
392
+ ```
393
+
394
+ ## Migration from Next.js
395
+
396
+ ### Before (Next.js)
397
+
398
+ ```typescript
399
+ // pages/api/hello.ts
400
+ import type { NextApiRequest, NextApiResponse } from 'next';
401
+
402
+ export default function handler(req: NextApiRequest, res: NextApiResponse) {
403
+ res.status(200).json({ message: 'Hello World' });
404
+ }
405
+ ```
406
+
407
+ ### After (Zuby.js with @zubyjs/next)
408
+
409
+ ```typescript
410
+ // pages/api/hello.ts
411
+ import type { NextApiRequest, NextApiResponse } from '@zubyjs/next';
412
+
413
+ export default function handler(req: NextApiRequest, res: NextApiResponse) {
414
+ return new Response(JSON.stringify({ message: 'Hello World' }), {
415
+ status: 200,
416
+ headers: { 'content-type': 'application/json' }
417
+ });
418
+ }
419
+ ```
420
+
421
+ The plugin automatically converts these routes - no need to manually rewrite them!
422
+
423
+ ## Version Compatibility
424
+
425
+ Ensure all zuby packages are in sync in your `package.json` file:
426
+
427
+ ```json
428
+ {
429
+ "name": "my-zuby-app",
430
+ "version": "1.0.0",
431
+ "dependencies": {
432
+ "zuby": "latest",
433
+ "@zubyjs/preact": "latest",
434
+ "@zubyjs/next": "latest"
435
+ }
436
+ }
437
+ ```
438
+
439
+ ## Contributing
440
+
441
+ This package is part of Zuby.js workspace and maintained by the team behind the [Zuby package](https://www.npmjs.com/package/zuby).
442
+ Please refer to it for more details how to contribute.
443
+
444
+ ## License
445
+
446
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ import type { ZubyPlugin } from 'zuby/types.js';
2
+ export interface NextPluginOptions {
3
+ /**
4
+ * Whether to automatically detect and convert Next.js pages/api routes
5
+ * @default true
6
+ */
7
+ autoDetectApiRoutes?: boolean;
8
+ /**
9
+ * Custom pages directory relative to srcDir
10
+ * @default ./pages
11
+ */
12
+ pagesDir?: string;
13
+ /**
14
+ * Whether to trace and copy dependencies of API routes to zuby-pages directory
15
+ * Uses @vercel/nft if available, falls back to manual import parsing
16
+ * @default true
17
+ */
18
+ traceDependencies?: boolean;
19
+ }
20
+ export declare const ZUBY_PAGES_DIR = "./zuby-pages";
21
+ /**
22
+ * Zuby.js Next.js compatibility plugin
23
+ * Converts Next.js pages and API routes to Zuby.js handlers
24
+ *
25
+ * Features:
26
+ * - Automatically scans ./pages directory
27
+ * - Converts ./pages/api routes to Zuby handlers
28
+ * - Converts React page components to Zuby pages
29
+ * - Supports Next.js API route syntax (get, post, put, delete, etc.)
30
+ * - Provides NextApiRequest and NextApiResponse compatibility objects
31
+ * - Handles both default and method-specific exports
32
+ * - Supports getStaticProps (prerender = true) and getServerSideProps (prerender = false)
33
+ * - Passes page props through context
34
+ *
35
+ * @param options Plugin options
36
+ * @returns ZubyPlugin
37
+ *
38
+ * @example
39
+ * // pages/api/hello.ts
40
+ * export default function handler(req, res) {
41
+ * return new Response('Hello from Zuby!');
42
+ * }
43
+ *
44
+ * // pages/api/posts/[id].ts
45
+ * export async function get(req, res) {
46
+ * const post = await fetchPost(req.query.id);
47
+ * return new Response(JSON.stringify(post));
48
+ * }
49
+ *
50
+ * export async function post(req, res) {
51
+ * const newPost = await createPost(req.body);
52
+ * return new Response(JSON.stringify(newPost), { status: 201 });
53
+ * }
54
+ *
55
+ * // pages/index.tsx with static generation
56
+ * export default function HomePage(props) {
57
+ * return <h1>Posts: {props.count}</h1>;
58
+ * }
59
+ *
60
+ * export async function getStaticProps() {
61
+ * return { props: { count: 42 } };
62
+ * }
63
+ *
64
+ * // pages/about.tsx with server-side rendering
65
+ * export default function AboutPage(props) {
66
+ * return <h1>About - User: {props.user}</h1>;
67
+ * }
68
+ *
69
+ * export async function getServerSideProps(context) {
70
+ * return { props: { user: 'John' } };
71
+ * }
72
+ */
73
+ declare const _default: ({ autoDetectApiRoutes, pagesDir, traceDependencies, }?: NextPluginOptions) => ZubyPlugin;
74
+ export default _default;
75
+ export { NextApiRequest, NextApiResponse, NextApiHandler, NextApiMethodHandler } from './types.js';
76
+ export { createNextApiRequest } from './nextApiRequest.js';
77
+ export { createNextApiResponse, nextApiResponseToResponse, getResponseState } from './nextApiResponse.js';