diff --git a/src/backend/src/middleware/anticsrf.js b/src/backend/src/middleware/anticsrf.js index 8ce4bf3f..e7844c57 100644 --- a/src/backend/src/middleware/anticsrf.js +++ b/src/backend/src/middleware/anticsrf.js @@ -19,6 +19,20 @@ const APIError = require("../api/APIError"); +/** + * Creates an anti-CSRF middleware that validates CSRF tokens in incoming requests. + * This middleware protects against Cross-Site Request Forgery attacks by verifying + * that requests contain a valid anti-CSRF token in the request body. + * + * @param {Object} options - Configuration options for the middleware + * @returns {Function} Express middleware function that validates CSRF tokens + * + * @example + * // Apply anti-CSRF protection to a route + * app.post('/api/secure-endpoint', anticsrf(), (req, res) => { + * // Route handler code + * }); + */ const anticsrf = options => async (req, res, next) => { const svc_antiCSRF = req.services.get('anti-csrf'); if ( ! req.body.anti_csrf ) { diff --git a/src/backend/src/modules/puterai/DeepSeekService.js b/src/backend/src/modules/puterai/DeepSeekService.js index 2cec41b4..16a263c9 100644 --- a/src/backend/src/modules/puterai/DeepSeekService.js +++ b/src/backend/src/modules/puterai/DeepSeekService.js @@ -179,24 +179,24 @@ class DeepSeekService extends BaseService { { id: 'deepseek-chat', name: 'DeepSeek Chat', - context: 64000, + context: 128000, cost: { currency: 'usd-cents', tokens: 1_000_000, - input: 14, - output: 28, + input: 56, + output: 168, }, max_tokens: 8000, }, { id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', - context: 64000, + context: 128000, cost: { currency: 'usd-cents', tokens: 1_000_000, - input: 55, - output: 219, + input: 56, + output: 168, }, max_tokens: 64000, } diff --git a/src/backend/src/services/auth/Actor.js b/src/backend/src/services/auth/Actor.js index ac55e334..7df3bc97 100644 --- a/src/backend/src/services/auth/Actor.js +++ b/src/backend/src/services/auth/Actor.js @@ -31,11 +31,11 @@ const PRIVATE_UID_SECRET = config.private_uid_secret /** -* Represents an Actor in the system, extending functionality from AdvancedBase. -* The Actor class is responsible for managing actor instances, including -* creating new actors, generating unique identifiers, and handling related types -* that represent different roles within the context of the application. -*/ + * Represents an Actor in the system, extending functionality from AdvancedBase. + * The Actor class is responsible for managing actor instances, including + * creating new actors, generating unique identifiers, and handling related types + * that represent different roles within the context of the application. + */ class Actor extends AdvancedBase { static MODULES = { uuidv5: require('uuid').v5, @@ -43,9 +43,9 @@ class Actor extends AdvancedBase { } static system_actor_ = null; + /** * Retrieves the system actor instance, creating it if it doesn't exist. - * * This static method ensures that there is only one instance of the system actor. * If the system actor has not yet been created, it will be instantiated with a * new SystemActorType. @@ -61,6 +61,16 @@ class Actor extends AdvancedBase { return this.system_actor_; } + /** + * Creates a new Actor instance with the specified type and parameters. + * Resolves user and app references from UIDs if provided in the parameters. + * + * @param {Function} type - The ActorType constructor to instantiate. + * @param {Object} params - Parameters for the actor type. + * @param {string} [params.user_uid] - UUID of the user to resolve. + * @param {string} [params.app_uid] - UID of the app to resolve. + * @returns {Promise} A new Actor instance. + */ static async create (type, params) { params = { ...params }; if ( params.user_uid ) { @@ -75,12 +85,12 @@ class Actor extends AdvancedBase { } /** - * Initializes the Actor instance with the provided parameters. - * This constructor assigns object properties from the input object to the instance. - * - * @param {Object} o - The object containing actor parameters. - * @param {...any} a - Additional arguments passed to the parent class constructor. - */ + * Initializes the Actor instance with the provided parameters. + * This constructor assigns object properties from the input object to the instance. + * + * @param {Object} o - The object containing actor parameters. + * @param {...any} a - Additional arguments passed to the parent class constructor. + */ constructor (o, ...a) { super(o, ...a); for ( const k in o ) { @@ -88,10 +98,20 @@ class Actor extends AdvancedBase { } } + /** + * Gets the unique identifier for this actor. + * + * @returns {string} The actor's UID from its type. + */ get uid () { return this.type.uid; } + /** + * Returns fields suitable for logging this actor. + * + * @returns {Object} Object containing UID and optionally username for logging. + */ toLogFields () { return { uid: this.type.uid, @@ -102,13 +122,13 @@ class Actor extends AdvancedBase { } /** - * Generates a cryptographically-secure deterministic UUID - * from an actor's UID. The generated UUID is derived by - * applying SHA-256 HMAC to the actor's UID using a secret, - * then formatting the result as a UUID V5. - * - * @returns {string} The derived UUID corresponding to the actor's UID. - */ + * Generates a cryptographically-secure deterministic UUID + * from an actor's UID. The generated UUID is derived by + * applying SHA-256 HMAC to the actor's UID using a secret, + * then formatting the result as a UUID V5. + * + * @returns {string} The derived UUID corresponding to the actor's UID. + */ get private_uid () { // Pass the UUID through SHA-2 first because UUIDv5 // is not cryptographically secure (it uses SHA-1) @@ -139,6 +159,12 @@ class Actor extends AdvancedBase { }); } + /** + * Creates a related actor of the specified type based on the current actor. + * + * @param {Function} type_class - The ActorType class to create a related actor for. + * @returns {Actor} A new Actor instance with the related type. + */ get_related_actor (type_class) { const actor = this.clone(); actor.type = this.type.get_related_type(type_class); @@ -146,7 +172,16 @@ class Actor extends AdvancedBase { } } +/** + * Base class for all actor types in the system. + * Provides common initialization functionality for actor type instances. + */ class ActorType { + /** + * Initializes the ActorType with the provided properties. + * + * @param {Object} o - Object containing properties to assign to this instance. + */ constructor (o) { for ( const k in o ) { this[k] = o[k]; @@ -155,15 +190,28 @@ class ActorType { } /** -* Class representing the system actor type within the actor framework. -* This type serves as a specific implementation of an actor that -* represents a system-level entity and provides methods for UID retrieval -* and related type management. -*/ + * Class representing the system actor type within the actor framework. + * This type serves as a specific implementation of an actor that + * represents a system-level entity and provides methods for UID retrieval + * and related type management. + */ class SystemActorType extends ActorType { + /** + * Gets the unique identifier for the system actor. + * + * @returns {string} Always returns 'system'. + */ get uid () { return 'system'; } + + /** + * Gets a related actor type for the system actor. + * + * @param {Function} type_class - The ActorType class to get a related type for. + * @returns {SystemActorType} Returns this instance if type_class is SystemActorType. + * @throws {Error} If the requested type_class is not supported. + */ get_related_type (type_class) { if ( type_class === SystemActorType ) { return this; @@ -174,14 +222,27 @@ class SystemActorType extends ActorType { /** -* Represents the type of a User Actor in the system, allowing operations and relations -* specific to user actors. This class extends the base functionality to uniquely identify -* user actors and define how they relate to other types of actors within the system. -*/ + * Represents the type of a User Actor in the system, allowing operations and relations + * specific to user actors. This class extends the base functionality to uniquely identify + * user actors and define how they relate to other types of actors within the system. + */ class UserActorType extends ActorType { + /** + * Gets the unique identifier for the user actor. + * + * @returns {string} The UID in format 'user:{uuid}'. + */ get uid () { return 'user:' + this.user.uuid; } + + /** + * Gets a related actor type for the user actor. + * + * @param {Function} type_class - The ActorType class to get a related type for. + * @returns {UserActorType} Returns this instance if type_class is UserActorType. + * @throws {Error} If the requested type_class is not supported. + */ get_related_type (type_class) { if ( type_class === UserActorType ) { return this; @@ -189,16 +250,30 @@ class UserActorType extends ActorType { throw new Error(`cannot get ${type_class.name} from ${this.constructor.name}`) } } + /** -* Represents a user actor type in the application. This class defines the structure -* and behavior specific to user actors, including obtaining unique identifiers and -* retrieving related actor types. It extends the base actor type functionality -* to cater to user-specific needs. -*/ + * Represents a user actor type in the application. This class defines the structure + * and behavior specific to user actors, including obtaining unique identifiers and + * retrieving related actor types. It extends the base actor type functionality + * to cater to user-specific needs. + */ class AppUnderUserActorType extends ActorType { + /** + * Gets the unique identifier for the app-under-user actor. + * + * @returns {string} The UID in format 'app-under-user:{user_uuid}:{app_uid}'. + */ get uid () { return 'app-under-user:' + this.user.uuid + ':' + this.app.uid; } + + /** + * Gets a related actor type for the app-under-user actor. + * + * @param {Function} type_class - The ActorType class to get a related type for. + * @returns {UserActorType|AppUnderUserActorType} The related actor type instance. + * @throws {Error} If the requested type_class is not supported. + */ get_related_type (type_class) { if ( type_class === UserActorType ) { return new UserActorType({ user: this.user }); @@ -212,26 +287,33 @@ class AppUnderUserActorType extends ActorType { /** -* Represents the type of access tokens in the system. -* An AccessTokenActorType associates an authorizer and an authorized actor -* with a string token, facilitating permission checks and identity management. -*/ + * Represents the type of access tokens in the system. + * An AccessTokenActorType associates an authorizer and an authorized actor + * with a string token, facilitating permission checks and identity management. + */ class AccessTokenActorType extends ActorType { // authorizer: an Actor who authorized the token // authorized: an Actor who is authorized by the token // token: a string + /** + * Gets the unique identifier for the access token actor. + * The UID is constructed based on the authorizer's UID, the authorized actor's UID (if available), + * and the token string. This UID format is useful for identifying the access token's context. + * + * @returns {string} The generated UID for the access token. + */ get uid () { return 'access-token:' + this.authorizer.uid + ':' + ( this.authorized?.uid ?? '' ) + ':' + this.token; } + /** - * Generate a unique identifier (UID) for the access token. - * The UID is constructed based on the authorizer's UID, the authorized actor's UID (if available), - * and the token string. This UID format is useful for identifying the access token's context. + * Throws an error as getting related actors is not supported for access tokens. + * This would be dangerous because of ambiguity between authorizer and authorized. * - * @returns {string} The generated UID for the access token. + * @throws {Error} Always throws an error indicating this operation is not supported. */ get_related_actor () { // This would be dangerous because of ambiguity @@ -242,29 +324,42 @@ class AccessTokenActorType extends ActorType { /** -* Represents a Site Actor Type, which encapsulates information about a site-specific actor. -* This class is used to manage details related to the site and implement functionalities -* pertinent to site-level operations and interactions in the actor framework. -*/ + * Represents a Site Actor Type, which encapsulates information about a site-specific actor. + * This class is used to manage details related to the site and implement functionalities + * pertinent to site-level operations and interactions in the actor framework. + */ class SiteActorType { /** - * Constructor for the SiteActorType class. - * Initializes a new instance of SiteActorType with the provided properties. - * - * @param {Object} o - The properties to initialize the SiteActorType with. - * @param {...*} a - Additional arguments. - */ + * Constructor for the SiteActorType class. + * Initializes a new instance of SiteActorType with the provided properties. + * + * @param {Object} o - The properties to initialize the SiteActorType with. + * @param {...*} a - Additional arguments. + */ constructor (o, ...a) { for ( const k in o ) { this[k] = o[k]; } } + /** + * Gets the unique identifier for the site actor. + * + * @returns {string} The UID in format 'site:{site_name}'. + */ get uid () { return `site:` + this.site.name } } +/** + * Adapts various input types to a proper Actor instance. + * If no actor is provided, attempts to get one from the current context. + * Handles legacy user objects by wrapping them in UserActorType. + * + * @param {Actor|Object} [actor] - The actor to adapt, or undefined to use context. + * @returns {Actor} A properly formatted Actor instance. + */ Actor.adapt = function (actor) { actor = actor || Context.get('actor'); diff --git a/src/backend/src/services/auth/AntiCSRFService.js b/src/backend/src/services/auth/AntiCSRFService.js index 3a8089eb..9750ebb1 100644 --- a/src/backend/src/services/auth/AntiCSRFService.js +++ b/src/backend/src/services/auth/AntiCSRFService.js @@ -30,6 +30,11 @@ const BaseService = require("../BaseService"); * A token expires when it is evicted from the queue. */ class CircularQueue { + /** + * Creates a new CircularQueue instance with the specified size. + * + * @param {number} size - The maximum number of items the queue can hold + */ constructor (size) { this.size = size; this.queue = []; @@ -37,6 +42,11 @@ class CircularQueue { this.map = new Map(); } + /** + * Adds an item to the queue. If the queue is full, the oldest item is removed. + * + * @param {*} item - The item to add to the queue + */ push (item) { if ( this.queue[this.index] ) { this.map.delete(this.queue[this.index]); @@ -46,14 +56,32 @@ class CircularQueue { this.index = (this.index + 1) % this.size; } + /** + * Retrieves an item from the queue at the specified relative index. + * + * @param {number} index - The relative index from the current position + * @returns {*} The item at the specified index + */ get (index) { return this.queue[(this.index + index) % this.size]; } + /** + * Checks if the queue contains the specified item. + * + * @param {*} item - The item to check for + * @returns {boolean} True if the item exists in the queue, false otherwise + */ has (item) { return this.map.has(item); } + /** + * Attempts to consume (remove) an item from the queue if it exists. + * + * @param {*} item - The item to consume + * @returns {boolean} True if the item was found and consumed, false otherwise + */ maybe_consume (item) { if ( this.has(item) ) { const index = this.map.get(item); @@ -81,6 +109,12 @@ class AntiCSRFService extends BaseService { this.map_session_to_tokens = {}; } + /** + * Sets up the route handler for getting anti-CSRF tokens. + * Registers the '/get-anticsrf-token' endpoint that returns a new token for authenticated users. + * + * @returns {void} + */ ['__on_install.routes'] () { const { app } = this.services.get('web-server'); @@ -106,6 +140,13 @@ class AntiCSRFService extends BaseService { })); } + /** + * Creates a new anti-CSRF token for the specified session. + * If no token queue exists for the session, a new one is created. + * + * @param {string} session - The session identifier + * @returns {string} The newly created token + */ create_token (session) { let tokens = this.map_session_to_tokens[session]; if ( ! tokens ) { @@ -117,6 +158,13 @@ class AntiCSRFService extends BaseService { return token; } + /** + * Attempts to consume (validate and remove) a token for the specified session. + * + * @param {string} session - The session identifier + * @param {string} token - The token to consume + * @returns {boolean} True if the token was valid and consumed, false otherwise + */ consume_token (session, token) { const tokens = this.map_session_to_tokens[session]; if ( ! tokens ) return false; @@ -135,6 +183,13 @@ class AntiCSRFService extends BaseService { return require('crypto').randomBytes(32).toString('hex'); } + /** + * Runs unit tests for the AntiCSRFService functionality. + * Tests token generation, expiration, and consumption behavior. + * + * @param {Object} params - Test parameters + * @param {Function} params.assert - Assertion function for testing + */ _test ({ assert }) { // Do this several times, like a user would for ( let i=0 ; i < 30 ; i++ ) { diff --git a/src/backend/src/services/file-cache/FileCacheService.js b/src/backend/src/services/file-cache/FileCacheService.js index cc254a16..e4aaea72 100644 --- a/src/backend/src/services/file-cache/FileCacheService.js +++ b/src/backend/src/services/file-cache/FileCacheService.js @@ -196,7 +196,7 @@ class FileCacheService extends AdvancedBase { // If the file is still in pending it means we waited too long; // it's possible that reading the file failed is is delayed. if ( tracker.phase === FileTracker.PHASE_PENDING ) { - return this._get_path(await fsNode.get('uid')); + return null; } // Since we waited for the file to be ready, it's not impossible diff --git a/src/dev-center/js/apps.js b/src/dev-center/js/apps.js index a26e3d26..fca9666c 100644 --- a/src/dev-center/js/apps.js +++ b/src/dev-center/js/apps.js @@ -612,6 +612,8 @@ function resetToOriginalValues() { } async function edit_app_section(cur_app_name, tab = 'deploy') { + puter.ui.showSpinner(); + $('section:not(.sidebar)').hide(); $('.tab-btn').removeClass('active'); $('.tab-btn[data-tab="apps"]').addClass('active'); diff --git a/src/gui/src/IPC.js b/src/gui/src/IPC.js index 8920ff45..98f4f851 100644 --- a/src/gui/src/IPC.js +++ b/src/gui/src/IPC.js @@ -442,9 +442,15 @@ const ipc_listener = async (event, handled) => { is_selectable_body = true; // Open dialog + let path = event.data.options?.path ?? '/' + window.user.username + '/Desktop'; + if ( (''+path).toLowerCase().startsWith('%appdata%') ) { + path = path.slice('%appdata%'.length); + if ( path !== '' && ! path.startsWith('/') ) path = '/' + path; + path = '/' + window.user.username + '/AppData/' + app_uuid + path; + } UIWindow({ allowed_file_types: allowed_file_types, - path: '/' + window.user.username + '/Desktop', + path, // this is the uuid of the window to which this dialog will return parent_uuid: event.data.appInstanceID, onDialogCancel: () => { diff --git a/src/putility/src/bases/BasicBase.js b/src/putility/src/bases/BasicBase.js index 228a2bb8..c64de9c9 100644 --- a/src/putility/src/bases/BasicBase.js +++ b/src/putility/src/bases/BasicBase.js @@ -16,7 +16,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + +/** + * Base class that provides utilities for working with inheritance chains and static properties. + */ class BasicBase { + /** + * Gets the inheritance chain for the current instance, starting from the most derived class + * and working up to BasicBase (excluded). + * @returns {Array} Array of constructor functions in inheritance order + */ _get_inheritance_chain () { const chain = []; let cls = this.constructor; @@ -27,6 +36,14 @@ class BasicBase { return chain.reverse(); } + /** + * Merges static array properties from all classes in the inheritance chain. + * Avoids duplicating the same array reference from contiguous members + * of the inheritance chain (useful when using the decorator pattern with + * multiple classes sharing a common base) + * @param {string} key - The name of the static property to merge + * @returns {Array} Combined array containing all values from the inheritance chain + */ _get_merged_static_array (key) { const chain = this._get_inheritance_chain(); const values = []; @@ -40,6 +57,12 @@ class BasicBase { return values; } + /** + * Merges static object properties from all classes in the inheritance chain. + * Properties from derived classes override those from base classes. + * @param {string} key - The name of the static property to merge + * @returns {Object} Combined object containing all properties from the inheritance chain + */ _get_merged_static_object (key) { // TODO: check objects by reference - same object in a subclass shouldn't count const chain = this._get_inheritance_chain(); diff --git a/src/putility/src/concepts/Service.js b/src/putility/src/concepts/Service.js index 82ceaa28..5c5a42e6 100644 --- a/src/putility/src/concepts/Service.js +++ b/src/putility/src/concepts/Service.js @@ -20,33 +20,59 @@ const { AdvancedBase } = require("../AdvancedBase"); const ServiceFeature = require("../features/ServiceFeature"); +/** @type {Function} No-operation async function */ const NOOP = async () => {}; -/** Service trait */ +/** @type {Symbol} Service trait symbol */ const TService = Symbol('TService'); /** - * Service will be incrementally updated to consolidate + * Service class that will be incrementally updated to consolidate * BaseService in Puter's backend with Service in Puter's frontend, * becoming the common base for both and a useful utility in general. + * + * @class Service + * @extends AdvancedBase */ class Service extends AdvancedBase { + /** @type {Array} Array of features this service supports */ static FEATURES = [ ServiceFeature, ]; + /** + * Handles events by calling the appropriate event handler + * + * @param {string} id - The event identifier + * @param {Array} args - Arguments to pass to the event handler + * @returns {Promise<*>} The result of the event handler + */ async __on (id, args) { const handler = this.__get_event_handler(id); return await handler(id, ...args); } + /** + * Retrieves the event handler for a given event ID + * + * @param {string} id - The event identifier + * @returns {Function} The event handler function or NOOP if not found + */ __get_event_handler (id) { return this[`__on_${id}`]?.bind?.(this) || this.constructor[`__on_${id}`]?.bind?.(this.constructor) || NOOP; } + /** + * Factory method to create a new service instance + * + * @param {Object} config - Configuration object + * @param {Object} config.parameters - Parameters for service construction + * @param {Object} config.context - Context for the service + * @returns {Service} A new service instance + */ static create ({ parameters, context }) { const ins = new this(); ins._.context = context; @@ -55,7 +81,14 @@ class Service extends AdvancedBase { } static IMPLEMENTS = { + /** @type {Object} Implementation of the TService trait */ [TService]: { + /** + * Initializes the service by running init hooks and calling _init if present + * + * @param {...*} a - Arguments to pass to _init method + * @returns {*} Result of _init method if it exists + */ init (...a) { if ( this._.init_hooks ) { for ( const hook of this._.init_hooks ) { @@ -65,12 +98,23 @@ class Service extends AdvancedBase { if ( ! this._init ) return; return this._init(...a); }, + /** + * Constructs the service with given parameters + * + * @param {Object} o - Parameters object + * @returns {*} Result of _construct method if it exists + */ construct (o) { this.$parameters = {}; for ( const k in o ) this.$parameters[k] = o[k]; if ( ! this._construct ) return; return this._construct(o); }, + /** + * Gets the dependencies for this service + * + * @returns {Array} Array of dependencies + */ get_depends () { return [ ...(this.constructor.DEPENDS ?? []), diff --git a/src/putility/src/libs/context.js b/src/putility/src/libs/context.js index 5888c56d..05e54991 100644 --- a/src/putility/src/libs/context.js +++ b/src/putility/src/libs/context.js @@ -17,13 +17,28 @@ * along with this program. If not, see . */ +/** + * A context object that manages hierarchical property inheritance and sub-context creation. + * Properties are copied with their descriptors to maintain getter/setter behavior. + */ class Context { + /** + * Creates a new Context instance with the provided values. + * @param {Object} [values={}] - Initial values to set on the context, with their property descriptors preserved + */ constructor (values = {}) { const descs = Object.getOwnPropertyDescriptors(values); for ( const k in descs ) { Object.defineProperty(this, k, descs[k]); } } + /** + * Creates a sub-context that follows specific properties from a source object. + * The returned context will have getters that reference the source object's properties. + * @param {Object} source - The source object to follow properties from + * @param {string[]} keys - Array of property names to follow from the source + * @returns {Context} A new sub-context with getters pointing to the source properties + */ follow (source, keys) { const values = {}; for ( const k of keys ) { @@ -33,6 +48,12 @@ class Context { } return this.sub(values); } + /** + * Creates a sub-context that inherits from the current context with additional or overridden values. + * Nested Context instances are recursively sub-contexted with corresponding new values. + * @param {Object} [newValues={}] - New values to add or override in the sub-context + * @returns {Context} A new context that inherits from this context with the new values applied + */ sub (newValues) { if ( newValues === undefined ) newValues = {}; const sub = Object.create(this); diff --git a/src/putility/src/libs/log.js b/src/putility/src/libs/log.js index 32d8fc66..198f85e7 100644 --- a/src/putility/src/libs/log.js +++ b/src/putility/src/libs/log.js @@ -20,6 +20,10 @@ const { AdvancedBase } = require("../AdvancedBase"); const { TLogger, AS } = require("../traits/traits"); +/** + * Logger implementation that stores log entries in an internal array buffer. + * Useful for testing or collecting log entries for later processing. + */ class ArrayLogger extends AdvancedBase { static PROPERTIES = { buffer: { @@ -28,6 +32,13 @@ class ArrayLogger extends AdvancedBase { } static IMPLEMENTS = { [TLogger]: { + /** + * Logs a message by storing it in the internal buffer array. + * @param {string} level - The log level (e.g., 'info', 'warn', 'error') + * @param {string} message - The log message + * @param {Object} fields - Additional fields to include with the log entry + * @param {Array} values - Additional values to log + */ log (level, message, fields, values) { this.buffer.push({ level, message, fields, values }); } @@ -35,6 +46,10 @@ class ArrayLogger extends AdvancedBase { } } +/** + * Logger that filters log entries based on enabled categories. + * Only logs messages for categories that have been explicitly enabled. + */ class CategorizedToggleLogger extends AdvancedBase { static PROPERTIES = { categories: { @@ -49,6 +64,14 @@ class CategorizedToggleLogger extends AdvancedBase { } static IMPLEMENTS = { [TLogger]: { + /** + * Logs a message only if the category specified in fields is enabled. + * @param {string} level - The log level + * @param {string} message - The log message + * @param {Object} fields - Fields object that should contain a 'category' property + * @param {Array} values - Additional values to log + * @returns {*} Result from delegate logger if category is enabled, undefined otherwise + */ log (level, message, fields, values) { const category = fields.category; if ( ! this.categories[category] ) return; @@ -56,14 +79,26 @@ class CategorizedToggleLogger extends AdvancedBase { } } } + /** + * Enables logging for the specified category. + * @param {string} category - The category to enable + */ on (category) { this.categories[category] = true; } + /** + * Disables logging for the specified category. + * @param {string} category - The category to disable + */ off (category) { delete this.categories[category]; } } +/** + * Logger that can be enabled or disabled globally. + * When disabled, all log messages are ignored. + */ class ToggleLogger extends AdvancedBase { static PROPERTIES = { enabled: { @@ -78,6 +113,14 @@ class ToggleLogger extends AdvancedBase { } static IMPLEMENTS = { [TLogger]: { + /** + * Logs a message only if the logger is enabled. + * @param {string} level - The log level + * @param {string} message - The log message + * @param {Object} fields - Additional fields to include + * @param {Array} values - Additional values to log + * @returns {*} Result from delegate logger if enabled, undefined otherwise + */ log (level, message, fields, values) { if ( ! this.enabled) return; return this.delegate.log(level, message, fields, values); @@ -86,6 +129,10 @@ class ToggleLogger extends AdvancedBase { } } +/** + * Logger that outputs formatted messages to the console. + * Supports colored output using ANSI escape codes and different log levels. + */ class ConsoleLogger extends AdvancedBase { static MODULES = { // This would be cool, if it worked in a browser. @@ -125,6 +172,13 @@ class ConsoleLogger extends AdvancedBase { } static IMPLEMENTS = { [TLogger]: { + /** + * Logs a formatted message to the console with color coding based on log level. + * @param {string} level - The log level (info, warn, error, debug) + * @param {string} message - The main log message + * @param {Object} fields - Additional fields to display + * @param {Array} values - Additional values to pass to console + */ log (level, message, fields, values) { const require = this.require; const util = require('util'); @@ -147,6 +201,9 @@ class ConsoleLogger extends AdvancedBase { } } +/** + * Logger that adds a prefix to all log messages before delegating to another logger. + */ class PrefixLogger extends AdvancedBase { static PROPERTIES = { prefix: { @@ -161,6 +218,14 @@ class PrefixLogger extends AdvancedBase { } static IMPLEMENTS = { [TLogger]: { + /** + * Logs a message with the configured prefix prepended to the message. + * @param {string} level - The log level + * @param {string} message - The original message + * @param {Object} fields - Additional fields to include + * @param {Array} values - Additional values to log + * @returns {*} Result from the delegate logger + */ log (level, message, fields, values) { return this.delegate.log( level, this.prefix + message, @@ -171,6 +236,9 @@ class PrefixLogger extends AdvancedBase { } } +/** + * Logger that adds default fields to all log entries before delegating to another logger. + */ class FieldsLogger extends AdvancedBase { static PROPERTIES = { fields: { @@ -186,6 +254,14 @@ class FieldsLogger extends AdvancedBase { static IMPLEMENTS = { [TLogger]: { + /** + * Logs a message with the configured default fields merged with provided fields. + * @param {string} level - The log level + * @param {string} message - The log message + * @param {Object} fields - Additional fields that will be merged with default fields + * @param {Array} values - Additional values to log + * @returns {*} Result from the delegate logger + */ log (level, message, fields, values) { return this.delegate.log( level, message, @@ -197,6 +273,10 @@ class FieldsLogger extends AdvancedBase { } } +/** + * Facade that provides a convenient interface for logging operations. + * Supports method chaining and category management. + */ class LoggerFacade extends AdvancedBase { static PROPERTIES = { impl: { @@ -213,12 +293,24 @@ class LoggerFacade extends AdvancedBase { static IMPLEMENTS = { [TLogger]: { + /** + * Basic log implementation (currently just outputs to console). + * @param {string} level - The log level + * @param {string} message - The log message + * @param {Object} fields - Additional fields + * @param {Array} values - Additional values + */ log (level, message, fields, values) { console.log() } } } + /** + * Creates a new logger facade with additional default fields. + * @param {Object} fields - Default fields to add to all log entries + * @returns {LoggerFacade} New logger facade instance with the specified fields + */ fields (fields) { const new_delegate = new FieldsLogger({ fields, @@ -229,13 +321,26 @@ class LoggerFacade extends AdvancedBase { }); } + /** + * Logs an info-level message. + * @param {string} message - The message to log + * @param {...*} values - Additional values to include in the log + */ info (message, ...values) { this.impl.log('info', message, {}, values); } + /** + * Enables logging for a specific category. + * @param {string} category - The category to enable + */ on (category) { this.cat.on(category); } + /** + * Disables logging for a specific category. + * @param {string} category - The category to disable + */ off (category) { this.cat.off(category); }