@pulzar/core
Version:
Next-generation Node.js framework for ultra-fast web applications with zero-reflection DI, GraphQL, WebSockets, events, and edge runtime support
336 lines • 10.1 kB
JavaScript
import { logger } from "../../utils/logger";
export class BaseResolver {
logger = logger;
/**
* Get authenticated user from context
*/
getUser(context) {
const user = context.auth?.user || context.user;
if (!user) {
throw new Error("Authentication required");
}
return user;
}
/**
* Get optional user from context
*/
getOptionalUser(context) {
return context.auth?.user || context.user || null;
}
/**
* Check if user is authenticated
*/
isAuthenticated(context) {
return !!(context.auth?.isAuthenticated || context.user);
}
/**
* Require authentication
*/
requireAuth(context) {
if (!this.isAuthenticated(context)) {
throw new Error("Authentication required");
}
}
/**
* Check if user has role
*/
hasRole(context, role) {
const auth = context.auth;
if (!auth)
return false;
return auth.hasRole(role);
}
/**
* Check if user has any of the roles
*/
hasAnyRole(context, roles) {
const auth = context.auth;
if (!auth)
return false;
return auth.hasAnyRole(roles);
}
/**
* Check if user has all roles
*/
hasAllRoles(context, roles) {
const auth = context.auth;
if (!auth)
return false;
return auth.hasAllRoles(roles);
}
/**
* Require specific role
*/
requireRole(context, role) {
this.requireAuth(context);
if (!this.hasRole(context, role)) {
throw new Error(`Role '${role}' required`);
}
}
/**
* Require any of the roles
*/
requireAnyRole(context, roles) {
this.requireAuth(context);
if (!this.hasAnyRole(context, roles)) {
throw new Error(`One of roles [${roles.join(", ")}] required`);
}
}
/**
* Check if user has permission
*/
hasPermission(context, permission) {
const auth = context.auth;
if (!auth)
return false;
return auth.hasPermission(permission);
}
/**
* Check if user has any of the permissions
*/
hasAnyPermission(context, permissions) {
const auth = context.auth;
if (!auth)
return false;
return auth.hasAnyPermission(permissions);
}
/**
* Check if user has all permissions
*/
hasAllPermissions(context, permissions) {
const auth = context.auth;
if (!auth)
return false;
return auth.hasAllPermissions(permissions);
}
/**
* Require specific permission
*/
requirePermission(context, permission) {
this.requireAuth(context);
if (!this.hasPermission(context, permission)) {
throw new Error(`Permission '${permission}' required`);
}
}
/**
* Require any of the permissions
*/
requireAnyPermission(context, permissions) {
this.requireAuth(context);
if (!this.hasAnyPermission(context, permissions)) {
throw new Error(`One of permissions [${permissions.join(", ")}] required`);
}
}
/**
* Get translation function from context
*/
getTranslationFunction(context) {
const t = context.i18n?.t || context.t;
if (!t) {
throw new Error("Translation function not available");
}
return (key, options) => {
if (typeof t === "function") {
return String(t(key, options));
}
return key;
};
}
/**
* Translate text
*/
translate(context, key, options) {
const t = this.getTranslationFunction(context);
return t(key, options);
}
/**
* Get current language from context
*/
getLanguage(context) {
return context.i18n?.language || context.language || "en";
}
/**
* Apply resolver options
*/
applyOptions(context, options) {
// Check authentication
if (options.requireAuth) {
this.requireAuth(context);
}
// Check roles
if (options.requiredRoles && options.requiredRoles.length > 0) {
this.requireAnyRole(context, options.requiredRoles);
}
// Check permissions
if (options.requiredPermissions && options.requiredPermissions.length > 0) {
this.requireAnyPermission(context, options.requiredPermissions);
}
// Rate limiting would be handled by middleware/directives
// Caching would be handled by middleware/directives
}
/**
* Handle resolver error
*/
handleError(error, context, fieldName) {
const user = this.getOptionalUser(context);
const requestId = context.req.headers["x-request-id"] || "unknown";
this.logger.error("GraphQL resolver error", {
error: error.message,
stack: error.stack,
fieldName,
userId: user?.id,
requestId,
});
// Don't expose internal errors in production
if (process.env.NODE_ENV === "production" &&
error.message.includes("INTERNAL")) {
throw new Error("Internal server error");
}
throw error;
}
/**
* Log resolver access
*/
logAccess(context, fieldName, args) {
const user = this.getOptionalUser(context);
const requestId = context.req.headers["x-request-id"] || "unknown";
this.logger.debug("GraphQL resolver access", {
fieldName,
args: args ? Object.keys(args) : undefined,
userId: user?.id,
requestId,
language: this.getLanguage(context),
});
}
/**
* Create standardized connection result for pagination
*/
createConnection(items, totalCount, limit, offset) {
const hasNextPage = offset + limit < totalCount;
const hasPreviousPage = offset > 0;
return {
edges: items.map((item, index) => ({
node: item,
cursor: Buffer.from((offset + index).toString()).toString("base64"),
})),
pageInfo: {
hasNextPage,
hasPreviousPage,
startCursor: items.length > 0
? Buffer.from(offset.toString()).toString("base64")
: null,
endCursor: items.length > 0
? Buffer.from((offset + items.length - 1).toString()).toString("base64")
: null,
},
totalCount,
};
}
/**
* Parse cursor for pagination
*/
parseCursor(cursor) {
try {
return parseInt(Buffer.from(cursor, "base64").toString(), 10);
}
catch (error) {
throw new Error("Invalid cursor");
}
}
/**
* Validate pagination arguments
*/
validatePagination(first, last, after, before) {
if (first && first < 0) {
throw new Error('Argument "first" must be a non-negative integer');
}
if (last && last < 0) {
throw new Error('Argument "last" must be a non-negative integer');
}
if (first && last) {
throw new Error('Cannot provide both "first" and "last" arguments');
}
if (!first && !last) {
throw new Error('Must provide either "first" or "last" argument');
}
// Limit maximum page size
const maxPageSize = 100;
if ((first && first > maxPageSize) || (last && last > maxPageSize)) {
throw new Error(`Maximum page size is ${maxPageSize}`);
}
}
/**
* Calculate pagination offset
*/
calculatePaginationOffset(after, before, first, last) {
this.validatePagination(first, last, after, before);
let offset = 0;
let limit = first || last || 10;
if (after) {
offset = this.parseCursor(after) + 1;
}
if (before) {
const beforeOffset = this.parseCursor(before);
if (last) {
offset = Math.max(0, beforeOffset - last);
limit = Math.min(last, beforeOffset - offset);
}
else {
limit = Math.min(limit, beforeOffset - offset);
}
}
return { limit, offset };
}
}
// Resolver decorator factory
export function Resolver(options = {}) {
return function (target) {
target.prototype._resolverOptions = options;
return target;
};
}
// Field decorator factory
export function Field(options = {}) {
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (parent, args, context, info) {
try {
// Apply options
if (this.applyOptions) {
this.applyOptions(context, options);
}
// Log access
if (this.logAccess) {
this.logAccess(context, info.fieldName, args);
}
return originalMethod.call(this, parent, args, context, info);
}
catch (error) {
if (this.handleError) {
this.handleError(error, context, info.fieldName);
}
throw error;
}
};
return descriptor;
};
}
// Auth decorators
export function RequireAuth() {
return Field({ requireAuth: true });
}
export function RequireRoles(...roles) {
return Field({ requiredRoles: roles });
}
export function RequirePermissions(...permissions) {
return Field({ requiredPermissions: permissions });
}
export function RateLimit(max, window) {
return Field({ rateLimit: { max, window } });
}
export function Cache(ttl, key) {
return Field({
cache: key ? { ttl, key } : { ttl },
});
}
//# sourceMappingURL=base.resolver.js.map