@mohasinac/next 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
23
+ };
24
+ var __copyProps = (to, from, except, desc) => {
25
+ if (from && typeof from === "object" || typeof from === "function") {
26
+ for (let key of __getOwnPropNames(from))
27
+ if (!__hasOwnProp.call(to, key) && key !== except)
28
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
+ }
30
+ return to;
31
+ };
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/index.ts
35
+ var index_exports = {};
36
+ __export(index_exports, {
37
+ createApiErrorHandler: () => createApiErrorHandler,
38
+ createRouteHandler: () => createRouteHandler
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/api/errorHandler.ts
43
+ var import_server = require("next/server");
44
+ function createApiErrorHandler(options) {
45
+ const {
46
+ isAppError,
47
+ getStatusCode,
48
+ toJSON,
49
+ logger,
50
+ internalErrorCode = "INTERNAL_ERROR",
51
+ internalErrorMessage = "An internal server error occurred"
52
+ } = options;
53
+ return function handleApiError(error) {
54
+ if (isAppError(error)) {
55
+ const status = getStatusCode(error);
56
+ if (status >= 500) {
57
+ logger.error("API Error", {
58
+ body: toJSON(error)
59
+ });
60
+ }
61
+ return import_server.NextResponse.json(toJSON(error), { status });
62
+ }
63
+ if (error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
64
+ return import_server.NextResponse.json(
65
+ {
66
+ success: false,
67
+ error: "Validation failed",
68
+ code: "VALIDATION_ERROR",
69
+ data: error
70
+ },
71
+ { status: 400 }
72
+ );
73
+ }
74
+ logger.error("Unexpected API error", {
75
+ error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : error
76
+ });
77
+ return import_server.NextResponse.json(
78
+ {
79
+ success: false,
80
+ error: internalErrorMessage,
81
+ code: internalErrorCode
82
+ },
83
+ { status: 500 }
84
+ );
85
+ };
86
+ }
87
+
88
+ // src/api/routeHandler.ts
89
+ var import_server2 = require("next/server");
90
+ var import_contracts = require("@mohasinac/contracts");
91
+ function readSessionCookie(request) {
92
+ var _a;
93
+ const cookieHeader = (_a = request.headers.get("cookie")) != null ? _a : "";
94
+ const match = cookieHeader.match(/(?:^|;\s*)__session=([^;]+)/);
95
+ return match ? decodeURIComponent(match[1]) : null;
96
+ }
97
+ async function verifySession(request) {
98
+ const { session } = (0, import_contracts.getProviders)();
99
+ if (!session) {
100
+ throw Object.assign(new Error("Session provider not configured"), {
101
+ status: 503
102
+ });
103
+ }
104
+ const cookie = readSessionCookie(request);
105
+ if (!cookie) {
106
+ throw Object.assign(new Error("Authentication required"), { status: 401 });
107
+ }
108
+ try {
109
+ const payload = await session.verifySession(cookie);
110
+ return __spreadValues({
111
+ uid: payload.uid,
112
+ email: payload.email,
113
+ role: payload.role,
114
+ emailVerified: payload.emailVerified
115
+ }, payload.claims);
116
+ } catch (e) {
117
+ throw Object.assign(new Error("Invalid or expired session"), {
118
+ status: 401
119
+ });
120
+ }
121
+ }
122
+ function createRouteHandler(options) {
123
+ return async (request, context) => {
124
+ var _a, _b, _c;
125
+ try {
126
+ let user;
127
+ const needsAuth = options.auth || options.roles && options.roles.length > 0;
128
+ if (needsAuth) {
129
+ user = await verifySession(request);
130
+ }
131
+ if (options.roles && options.roles.length > 0) {
132
+ if (!user || !options.roles.includes((_a = user.role) != null ? _a : "")) {
133
+ return import_server2.NextResponse.json(
134
+ { success: false, error: "Insufficient permissions" },
135
+ { status: 403 }
136
+ );
137
+ }
138
+ }
139
+ let body;
140
+ if (options.schema) {
141
+ let raw;
142
+ try {
143
+ raw = await request.json();
144
+ } catch (e) {
145
+ return import_server2.NextResponse.json(
146
+ { success: false, error: "Invalid JSON body" },
147
+ { status: 400 }
148
+ );
149
+ }
150
+ const result = options.schema.safeParse(raw);
151
+ if (!result.success) {
152
+ return import_server2.NextResponse.json(
153
+ {
154
+ success: false,
155
+ error: "Validation failed",
156
+ issues: (_c = (_b = result.error) == null ? void 0 : _b.issues) != null ? _c : []
157
+ },
158
+ { status: 400 }
159
+ );
160
+ }
161
+ body = result.data;
162
+ }
163
+ const params = (context == null ? void 0 : context.params) ? await context.params : void 0;
164
+ return await options.handler({
165
+ request,
166
+ user,
167
+ body,
168
+ params
169
+ });
170
+ } catch (err) {
171
+ const status = typeof (err == null ? void 0 : err.status) === "number" ? err.status : 500;
172
+ const message = err instanceof Error ? err.message : "Internal server error";
173
+ console.error(`[createRouteHandler] ${request.method} failed`, err);
174
+ return import_server2.NextResponse.json(
175
+ { success: false, error: message },
176
+ { status }
177
+ );
178
+ }
179
+ };
180
+ }
181
+ // Annotate the CommonJS export names for ESM import in node:
182
+ 0 && (module.exports = {
183
+ createApiErrorHandler,
184
+ createRouteHandler
185
+ });
@@ -0,0 +1,175 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * IAuthVerifier — Injectable auth interface for createApiHandler.
5
+ *
6
+ * Decouples @mohasinac/next from any specific auth provider (Firebase, Auth.js, etc.).
7
+ * The consuming app provides a concrete implementation backed by its own
8
+ * auth SDK. The interface is intentionally minimal — only what createApiHandler
9
+ * needs: a uid and an optional role string.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // apps/web/src/lib/firebase/auth-verifier.ts
14
+ * import type { IAuthVerifier, AuthVerifiedUser } from '@mohasinac/next';
15
+ * import { getAdminAuth } from '@/lib/firebase/admin';
16
+ *
17
+ * export const firebaseAuthVerifier: IAuthVerifier = {
18
+ * async verify(sessionCookie) {
19
+ * const decoded = await getAdminAuth().verifySessionCookie(sessionCookie, true);
20
+ * return { uid: decoded.uid, role: decoded.role as string | undefined };
21
+ * },
22
+ * };
23
+ * ```
24
+ */
25
+ /**
26
+ * Minimal user shape returned by a successful auth verification.
27
+ * Implementors may return additional fields; createApiHandler only consumes
28
+ * `uid` and `role`.
29
+ */
30
+ interface AuthVerifiedUser {
31
+ uid: string;
32
+ email?: string;
33
+ role?: string;
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * Generic auth verifier interface.
38
+ *
39
+ * An implementation should:
40
+ * - Verify the session token/cookie cryptographically.
41
+ * - Throw or return `null` when verification fails (both are handled by
42
+ * createApiHandler — returning null triggers a 401; throwing triggers
43
+ * handleApiError which maps to a 401 for AuthenticationError subclasses).
44
+ */
45
+ interface IAuthVerifier {
46
+ /**
47
+ * Verify a session token (cookie value, JWT, or any string credential) and
48
+ * return the decoded user payload, or `null` if invalid.
49
+ *
50
+ * @param token - Raw session token (typically an httpOnly cookie value).
51
+ * @returns Verified user, or `null` when the token is expired / invalid.
52
+ * @throws May throw an `AuthenticationError` subclass; createApiHandler
53
+ * will convert it to a 401 response via handleApiError.
54
+ */
55
+ verify(token: string): Promise<AuthVerifiedUser | null>;
56
+ }
57
+
58
+ /**
59
+ * Generic API error handler for Next.js API routes.
60
+ *
61
+ * Framework-agnostic version of the app's handleApiError. Business-specific
62
+ * error classes and loggers are injected via factory options so this module
63
+ * has zero knowledge of Firebase, Resend, or letitrip.in domain logic.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * // apps/web/src/lib/api/error-handler.ts
68
+ * import { createApiErrorHandler } from '@mohasinac/next';
69
+ * import { AppError } from '@/lib/errors';
70
+ * import { serverLogger } from '@/lib/server-logger';
71
+ *
72
+ * export const handleApiError = createApiErrorHandler({
73
+ * isAppError: (e): e is AppError => e instanceof AppError,
74
+ * getStatusCode: (e) => e.statusCode,
75
+ * toJSON: (e) => e.toJSON(),
76
+ * logger: serverLogger,
77
+ * internalErrorCode: 'GEN_INTERNAL_ERROR',
78
+ * internalErrorMessage: 'An internal server error occurred',
79
+ * });
80
+ * ```
81
+ */
82
+
83
+ /** Minimal logger interface — satisfied by Winston, Pino, or console. */
84
+ interface IApiErrorLogger {
85
+ error(message: string, meta?: Record<string, unknown>): void;
86
+ }
87
+ /** Options for createApiErrorHandler factory. */
88
+ interface ApiErrorHandlerOptions<TAppError> {
89
+ /**
90
+ * Type guard — returns true when the thrown value is your app's AppError
91
+ * (or any subclass) that has statusCode / toJSON methods.
92
+ */
93
+ isAppError(err: unknown): err is TAppError;
94
+ /** Extract the HTTP status code from your AppError. */
95
+ getStatusCode(err: TAppError): number;
96
+ /** Serialise the error to a JSON-safe shape for the response body. */
97
+ toJSON(err: TAppError): unknown;
98
+ /** Logger used for 5xx and unexpected errors. */
99
+ logger: IApiErrorLogger;
100
+ /** Error code included in the 500 response body. */
101
+ internalErrorCode?: string;
102
+ /** Error message included in the 500 response body. */
103
+ internalErrorMessage?: string;
104
+ }
105
+ /**
106
+ * Build a typed handleApiError function bound to your app's error hierarchy.
107
+ *
108
+ * The returned function:
109
+ * - Returns a typed NextResponse for known AppError subclasses.
110
+ * - Returns a 400 with validation details for Zod-like error shapes.
111
+ * - Logs unexpected errors server-side; returns a generic 500 (no stack trace
112
+ * leaks to the client).
113
+ */
114
+ declare function createApiErrorHandler<TAppError>(options: ApiErrorHandlerOptions<TAppError>): (error: unknown) => NextResponse;
115
+
116
+ /**
117
+ * createRouteHandler — provider-aware API route handler factory for feat-* packages.
118
+ *
119
+ * Works like letitrip.in's local `createApiHandler` but uses `getProviders().session`
120
+ * for auth instead of a hardwired Firebase Admin call. This makes it portable across
121
+ * all consumer projects that register an `ISessionProvider`.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * export const POST = createRouteHandler({
126
+ * auth: true,
127
+ * roles: ["admin"],
128
+ * schema: mySchema,
129
+ * handler: async ({ user, body }) => { ... },
130
+ * });
131
+ * ```
132
+ */
133
+
134
+ /** Minimal schema interface compatible with Zod v3 and v4. */
135
+ interface ParseableSchema<TOutput> {
136
+ safeParse(data: unknown): {
137
+ success: true;
138
+ data: TOutput;
139
+ } | {
140
+ success: false;
141
+ error: any;
142
+ };
143
+ }
144
+ interface RouteUser {
145
+ uid: string;
146
+ email?: string | null;
147
+ role?: string;
148
+ [key: string]: unknown;
149
+ }
150
+ interface RouteHandlerOptions<TInput = unknown, TParams = Record<string, string>> {
151
+ /** Require a valid session cookie. Implied when `roles` is set. */
152
+ auth?: boolean;
153
+ /**
154
+ * If provided, the verified user's `role` must be in this list.
155
+ * Implies `auth: true`.
156
+ */
157
+ roles?: string[];
158
+ /** Zod schema to validate + parse the JSON request body. */
159
+ schema?: ParseableSchema<TInput>;
160
+ /** Route handler. `user` is present when `auth: true`. */
161
+ handler: (ctx: {
162
+ request: Request;
163
+ user?: RouteUser;
164
+ body?: TInput;
165
+ params?: TParams;
166
+ }) => Promise<any>;
167
+ }
168
+ /**
169
+ * Create a typed Next.js App Router handler with built-in auth + Zod validation.
170
+ */
171
+ declare function createRouteHandler<TInput = unknown, TParams = Record<string, string>>(options: RouteHandlerOptions<TInput, TParams>): (request: Request, context: {
172
+ params: Promise<TParams>;
173
+ }) => Promise<NextResponse>;
174
+
175
+ export { type ApiErrorHandlerOptions, type AuthVerifiedUser, type IApiErrorLogger, type IAuthVerifier, type RouteUser, createApiErrorHandler, createRouteHandler };
@@ -0,0 +1,175 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * IAuthVerifier — Injectable auth interface for createApiHandler.
5
+ *
6
+ * Decouples @mohasinac/next from any specific auth provider (Firebase, Auth.js, etc.).
7
+ * The consuming app provides a concrete implementation backed by its own
8
+ * auth SDK. The interface is intentionally minimal — only what createApiHandler
9
+ * needs: a uid and an optional role string.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // apps/web/src/lib/firebase/auth-verifier.ts
14
+ * import type { IAuthVerifier, AuthVerifiedUser } from '@mohasinac/next';
15
+ * import { getAdminAuth } from '@/lib/firebase/admin';
16
+ *
17
+ * export const firebaseAuthVerifier: IAuthVerifier = {
18
+ * async verify(sessionCookie) {
19
+ * const decoded = await getAdminAuth().verifySessionCookie(sessionCookie, true);
20
+ * return { uid: decoded.uid, role: decoded.role as string | undefined };
21
+ * },
22
+ * };
23
+ * ```
24
+ */
25
+ /**
26
+ * Minimal user shape returned by a successful auth verification.
27
+ * Implementors may return additional fields; createApiHandler only consumes
28
+ * `uid` and `role`.
29
+ */
30
+ interface AuthVerifiedUser {
31
+ uid: string;
32
+ email?: string;
33
+ role?: string;
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * Generic auth verifier interface.
38
+ *
39
+ * An implementation should:
40
+ * - Verify the session token/cookie cryptographically.
41
+ * - Throw or return `null` when verification fails (both are handled by
42
+ * createApiHandler — returning null triggers a 401; throwing triggers
43
+ * handleApiError which maps to a 401 for AuthenticationError subclasses).
44
+ */
45
+ interface IAuthVerifier {
46
+ /**
47
+ * Verify a session token (cookie value, JWT, or any string credential) and
48
+ * return the decoded user payload, or `null` if invalid.
49
+ *
50
+ * @param token - Raw session token (typically an httpOnly cookie value).
51
+ * @returns Verified user, or `null` when the token is expired / invalid.
52
+ * @throws May throw an `AuthenticationError` subclass; createApiHandler
53
+ * will convert it to a 401 response via handleApiError.
54
+ */
55
+ verify(token: string): Promise<AuthVerifiedUser | null>;
56
+ }
57
+
58
+ /**
59
+ * Generic API error handler for Next.js API routes.
60
+ *
61
+ * Framework-agnostic version of the app's handleApiError. Business-specific
62
+ * error classes and loggers are injected via factory options so this module
63
+ * has zero knowledge of Firebase, Resend, or letitrip.in domain logic.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * // apps/web/src/lib/api/error-handler.ts
68
+ * import { createApiErrorHandler } from '@mohasinac/next';
69
+ * import { AppError } from '@/lib/errors';
70
+ * import { serverLogger } from '@/lib/server-logger';
71
+ *
72
+ * export const handleApiError = createApiErrorHandler({
73
+ * isAppError: (e): e is AppError => e instanceof AppError,
74
+ * getStatusCode: (e) => e.statusCode,
75
+ * toJSON: (e) => e.toJSON(),
76
+ * logger: serverLogger,
77
+ * internalErrorCode: 'GEN_INTERNAL_ERROR',
78
+ * internalErrorMessage: 'An internal server error occurred',
79
+ * });
80
+ * ```
81
+ */
82
+
83
+ /** Minimal logger interface — satisfied by Winston, Pino, or console. */
84
+ interface IApiErrorLogger {
85
+ error(message: string, meta?: Record<string, unknown>): void;
86
+ }
87
+ /** Options for createApiErrorHandler factory. */
88
+ interface ApiErrorHandlerOptions<TAppError> {
89
+ /**
90
+ * Type guard — returns true when the thrown value is your app's AppError
91
+ * (or any subclass) that has statusCode / toJSON methods.
92
+ */
93
+ isAppError(err: unknown): err is TAppError;
94
+ /** Extract the HTTP status code from your AppError. */
95
+ getStatusCode(err: TAppError): number;
96
+ /** Serialise the error to a JSON-safe shape for the response body. */
97
+ toJSON(err: TAppError): unknown;
98
+ /** Logger used for 5xx and unexpected errors. */
99
+ logger: IApiErrorLogger;
100
+ /** Error code included in the 500 response body. */
101
+ internalErrorCode?: string;
102
+ /** Error message included in the 500 response body. */
103
+ internalErrorMessage?: string;
104
+ }
105
+ /**
106
+ * Build a typed handleApiError function bound to your app's error hierarchy.
107
+ *
108
+ * The returned function:
109
+ * - Returns a typed NextResponse for known AppError subclasses.
110
+ * - Returns a 400 with validation details for Zod-like error shapes.
111
+ * - Logs unexpected errors server-side; returns a generic 500 (no stack trace
112
+ * leaks to the client).
113
+ */
114
+ declare function createApiErrorHandler<TAppError>(options: ApiErrorHandlerOptions<TAppError>): (error: unknown) => NextResponse;
115
+
116
+ /**
117
+ * createRouteHandler — provider-aware API route handler factory for feat-* packages.
118
+ *
119
+ * Works like letitrip.in's local `createApiHandler` but uses `getProviders().session`
120
+ * for auth instead of a hardwired Firebase Admin call. This makes it portable across
121
+ * all consumer projects that register an `ISessionProvider`.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * export const POST = createRouteHandler({
126
+ * auth: true,
127
+ * roles: ["admin"],
128
+ * schema: mySchema,
129
+ * handler: async ({ user, body }) => { ... },
130
+ * });
131
+ * ```
132
+ */
133
+
134
+ /** Minimal schema interface compatible with Zod v3 and v4. */
135
+ interface ParseableSchema<TOutput> {
136
+ safeParse(data: unknown): {
137
+ success: true;
138
+ data: TOutput;
139
+ } | {
140
+ success: false;
141
+ error: any;
142
+ };
143
+ }
144
+ interface RouteUser {
145
+ uid: string;
146
+ email?: string | null;
147
+ role?: string;
148
+ [key: string]: unknown;
149
+ }
150
+ interface RouteHandlerOptions<TInput = unknown, TParams = Record<string, string>> {
151
+ /** Require a valid session cookie. Implied when `roles` is set. */
152
+ auth?: boolean;
153
+ /**
154
+ * If provided, the verified user's `role` must be in this list.
155
+ * Implies `auth: true`.
156
+ */
157
+ roles?: string[];
158
+ /** Zod schema to validate + parse the JSON request body. */
159
+ schema?: ParseableSchema<TInput>;
160
+ /** Route handler. `user` is present when `auth: true`. */
161
+ handler: (ctx: {
162
+ request: Request;
163
+ user?: RouteUser;
164
+ body?: TInput;
165
+ params?: TParams;
166
+ }) => Promise<any>;
167
+ }
168
+ /**
169
+ * Create a typed Next.js App Router handler with built-in auth + Zod validation.
170
+ */
171
+ declare function createRouteHandler<TInput = unknown, TParams = Record<string, string>>(options: RouteHandlerOptions<TInput, TParams>): (request: Request, context: {
172
+ params: Promise<TParams>;
173
+ }) => Promise<NextResponse>;
174
+
175
+ export { type ApiErrorHandlerOptions, type AuthVerifiedUser, type IApiErrorLogger, type IAuthVerifier, type RouteUser, createApiErrorHandler, createRouteHandler };
package/dist/index.js ADDED
@@ -0,0 +1,160 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
18
+ // src/api/errorHandler.ts
19
+ import { NextResponse } from "next/server";
20
+ function createApiErrorHandler(options) {
21
+ const {
22
+ isAppError,
23
+ getStatusCode,
24
+ toJSON,
25
+ logger,
26
+ internalErrorCode = "INTERNAL_ERROR",
27
+ internalErrorMessage = "An internal server error occurred"
28
+ } = options;
29
+ return function handleApiError(error) {
30
+ if (isAppError(error)) {
31
+ const status = getStatusCode(error);
32
+ if (status >= 500) {
33
+ logger.error("API Error", {
34
+ body: toJSON(error)
35
+ });
36
+ }
37
+ return NextResponse.json(toJSON(error), { status });
38
+ }
39
+ if (error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
40
+ return NextResponse.json(
41
+ {
42
+ success: false,
43
+ error: "Validation failed",
44
+ code: "VALIDATION_ERROR",
45
+ data: error
46
+ },
47
+ { status: 400 }
48
+ );
49
+ }
50
+ logger.error("Unexpected API error", {
51
+ error: error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : error
52
+ });
53
+ return NextResponse.json(
54
+ {
55
+ success: false,
56
+ error: internalErrorMessage,
57
+ code: internalErrorCode
58
+ },
59
+ { status: 500 }
60
+ );
61
+ };
62
+ }
63
+
64
+ // src/api/routeHandler.ts
65
+ import { NextResponse as NextResponse2 } from "next/server";
66
+ import { getProviders } from "@mohasinac/contracts";
67
+ function readSessionCookie(request) {
68
+ var _a;
69
+ const cookieHeader = (_a = request.headers.get("cookie")) != null ? _a : "";
70
+ const match = cookieHeader.match(/(?:^|;\s*)__session=([^;]+)/);
71
+ return match ? decodeURIComponent(match[1]) : null;
72
+ }
73
+ async function verifySession(request) {
74
+ const { session } = getProviders();
75
+ if (!session) {
76
+ throw Object.assign(new Error("Session provider not configured"), {
77
+ status: 503
78
+ });
79
+ }
80
+ const cookie = readSessionCookie(request);
81
+ if (!cookie) {
82
+ throw Object.assign(new Error("Authentication required"), { status: 401 });
83
+ }
84
+ try {
85
+ const payload = await session.verifySession(cookie);
86
+ return __spreadValues({
87
+ uid: payload.uid,
88
+ email: payload.email,
89
+ role: payload.role,
90
+ emailVerified: payload.emailVerified
91
+ }, payload.claims);
92
+ } catch (e) {
93
+ throw Object.assign(new Error("Invalid or expired session"), {
94
+ status: 401
95
+ });
96
+ }
97
+ }
98
+ function createRouteHandler(options) {
99
+ return async (request, context) => {
100
+ var _a, _b, _c;
101
+ try {
102
+ let user;
103
+ const needsAuth = options.auth || options.roles && options.roles.length > 0;
104
+ if (needsAuth) {
105
+ user = await verifySession(request);
106
+ }
107
+ if (options.roles && options.roles.length > 0) {
108
+ if (!user || !options.roles.includes((_a = user.role) != null ? _a : "")) {
109
+ return NextResponse2.json(
110
+ { success: false, error: "Insufficient permissions" },
111
+ { status: 403 }
112
+ );
113
+ }
114
+ }
115
+ let body;
116
+ if (options.schema) {
117
+ let raw;
118
+ try {
119
+ raw = await request.json();
120
+ } catch (e) {
121
+ return NextResponse2.json(
122
+ { success: false, error: "Invalid JSON body" },
123
+ { status: 400 }
124
+ );
125
+ }
126
+ const result = options.schema.safeParse(raw);
127
+ if (!result.success) {
128
+ return NextResponse2.json(
129
+ {
130
+ success: false,
131
+ error: "Validation failed",
132
+ issues: (_c = (_b = result.error) == null ? void 0 : _b.issues) != null ? _c : []
133
+ },
134
+ { status: 400 }
135
+ );
136
+ }
137
+ body = result.data;
138
+ }
139
+ const params = (context == null ? void 0 : context.params) ? await context.params : void 0;
140
+ return await options.handler({
141
+ request,
142
+ user,
143
+ body,
144
+ params
145
+ });
146
+ } catch (err) {
147
+ const status = typeof (err == null ? void 0 : err.status) === "number" ? err.status : 500;
148
+ const message = err instanceof Error ? err.message : "Internal server error";
149
+ console.error(`[createRouteHandler] ${request.method} failed`, err);
150
+ return NextResponse2.json(
151
+ { success: false, error: message },
152
+ { status }
153
+ );
154
+ }
155
+ };
156
+ }
157
+ export {
158
+ createApiErrorHandler,
159
+ createRouteHandler
160
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@mohasinac/next",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup src/index.ts --format esm,cjs --dts",
23
+ "test": "vitest run",
24
+ "lint": "eslint src"
25
+ },
26
+ "peerDependencies": {
27
+ "next": ">=14",
28
+ "react": ">=18",
29
+ "zod": ">=3"
30
+ },
31
+ "dependencies": {
32
+ "@mohasinac/contracts": "workspace:*"
33
+ },
34
+ "devDependencies": {
35
+ "tsup": "^8.5.0",
36
+ "typescript": "^5.9.3",
37
+ "vitest": "^3.2.4",
38
+ "eslint": "^9.37.0"
39
+ }
40
+ }