UNPKG

@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
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