{"version":3,"file":"Model.cjs","sources":["../src/Model.js"],"sourcesContent":["import Emitter from './Emitter.js';\nimport getResult from './utils/getResult.js';\n\n/**\n * - Orchestrates data and business logic.\n * - Emits events when data changes.\n * \n * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified.  \n * Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects \n * that contain all the necessary functions for manipulating their specific data.  \n * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.\n * \n * ## Construction Flow\n * 1. `preinitialize()` is called with all constructor arguments\n * 2. `this.defaults` are resolved (if function, it's called and bound to the model)\n * 3. `parse()` is called with all constructor arguments to process the data\n * 4. `this.attributes` is built by merging defaults and parsed data\n * 5. Getters/setters are generated for each attribute to emit change events\n * \n * @module\n * @extends Emitter\n * @param {object} [attributes={}] Primary data object containing model attributes\n * @param {...*} [args] Additional arguments passed to `preinitialize` and `parse` methods\n * @property {object|Function} defaults Default attributes for the model. If a function, it's called bound to the model instance to get defaults.\n * @property {object} previous Object containing previous attributes when a change occurs.\n * @property {string} attributePrefix Static property that defines a prefix for generated getters/setters. Defaults to empty string.\n * @example\n * import { Model } from 'rasti';\n * \n * // User model\n * class User extends Model {\n *     preinitialize() {\n *         this.defaults = { name : '', email : '', role : 'user' };\n *     }\n * }\n * // Order model with nested User and custom methods\n * class Order extends Model {\n *     preinitialize(attributes, options = {}) {\n *         this.defaults = {\n *             id : null,\n *             total : 0,\n *             status : 'pending',\n *             user : null\n *         };\n * \n *         this.apiUrl = options.apiUrl || '/api/orders';\n *     }\n *\n *     parse(data, options = {}) {\n *         const parsed = { ...data };\n *         \n *         // Convert user object to User model instance\n *         if (data.user && !(data.user instanceof User)) {\n *             parsed.user = new User(data.user);\n *         }\n * \n *         return parsed;\n *     }\n * \n *     toJSON() {\n *         const result = {};\n *         for (const [key, value] of Object.entries(this.attributes)) {\n *             if (value instanceof Model) {\n *                 result[key] = value.toJSON();\n *             } else {\n *                 result[key] = value;\n *             }\n *         }\n *         return result;\n *     }\n * \n *     async fetch() {\n *         try {\n *             const response = await fetch(`${this.apiUrl}/${this.id}`);\n *             const data = await response.json();\n *             \n *             // Parse the fetched data and update model\n *             const parsed = this.parse(data);\n *             this.set(parsed, { source : 'fetch' });\n * \n *             return this;\n *         } catch (error) {\n *             console.error('Failed to fetch order:', error);\n *             throw error;\n *         }\n *     }\n * }\n * \n * // Create order with nested user data\n * const order = new Order({\n *     id : 123,\n *     total : 99.99,\n *     user : { name : 'Alice', email : 'alice@example.com' }\n * });\n * \n * console.log(order.user instanceof User); // true\n * // Serialize with nested models\n * const json = order.toJSON();\n * console.log(json); // { id: 123, total: 99.99, status: 'pending', user: { name: 'Alice', email: 'alice@example.com', role: 'user' } }\n * \n * // Listen to fetch updates\n * order.on('change', (model, changed, options) => {\n *     if (options?.source === 'fetch') {\n *         console.log('Order updated from server:', changed);\n *     }\n * });\n * \n * // Fetch latest data from server\n * await order.fetch();\n */\nexport default class Model extends Emitter {\n    constructor() {\n        super();\n        // Call preinitialize.\n        this.preinitialize.apply(this, arguments);\n        // Set attributes object with defaults and passed attributes.\n        this.attributes = Object.assign({}, getResult(this.defaults, this), this.parse.apply(this, arguments));\n        // Object to store previous attributes when a change occurs.\n        this.previous = {};\n        // Generate getters/setters for every attribute.\n        Object.keys(this.attributes).forEach(this.defineAttribute.bind(this));\n    }\n\n    /**\n     * Called before any instantiation logic runs for the Model.\n     * Receives all constructor arguments, allowing for flexible initialization patterns.\n     * Use this to set up `defaults`, configure the model, or handle custom constructor arguments.\n     * @param {object} [attributes={}] Primary data object containing model attributes\n     * @param {...*} [args] Additional arguments passed from the constructor\n     * @example\n     * class User extends Model {\n     *     preinitialize(attributes, options = {}) {\n     *         this.defaults = { name : '', role : options.defaultRole || 'user' };\n     *         this.apiEndpoint = options.apiEndpoint || '/users';\n     *     }\n     * }\n     * const user = new User({ name : 'Alice' }, { defaultRole : 'admin', apiEndpoint : '/api/users' });\n     */\n    preinitialize() {}\n\n    /**\n     * Generate getter/setter for the given attribute key to emit `change` events.\n     * The property name uses `attributePrefix` + key (e.g., with prefix 'attr_', key 'name' becomes 'attr_name').\n     * Called internally by the constructor for each key in `this.attributes`.\n     * Override with an empty method if you don't want automatic getters/setters.\n     * \n     * @param {string} key Attribute key from `this.attributes`\n     * @example\n     * // Custom prefix for all attributes\n     * class PrefixedModel extends Model {\n     *     static attributePrefix = 'attr_';\n     * }\n     * const model = new PrefixedModel({ name: 'Alice' });\n     * console.log(model.attr_name); // 'Alice'\n     * \n     * // Disable automatic getters/setters\n     * class ManualModel extends Model {\n     *     defineAttribute() {\n     *         // Empty - no getters/setters generated\n     *     }\n     *     \n     *     getName() {\n     *         return this.get('name'); // Manual getter\n     *     }\n     * }\n     */\n    defineAttribute(key) {\n        Object.defineProperty(\n            this,\n            `${this.constructor.attributePrefix}${key}`,\n            {\n                get : () => this.get(key),\n                set : (value) => { this.set(key, value); }\n            }\n        );\n    }\n\n    /**\n     * Get an attribute from `this.attributes`.\n     * This method is called internally by generated getters.\n     * @param {string} key Attribute key.\n     * @return {any} The attribute value.\n     */\n    get(key) {\n        return this.attributes[key];\n    }\n\n    /**\n     * Set one or more attributes into `this.attributes` and emit change events.\n     * Supports two call signatures: `set(key, value, ...args)` or `set(object, ...args)`.\n     * Additional arguments are passed to change event listeners, enabling custom behavior.\n     * \n     * @param {string|object} key Attribute key (string) or object containing key-value pairs\n     * @param {*} [value] Attribute value (when key is string)\n     * @param {...*} [args] Additional arguments passed to event listeners\n     * @return {Model} This model instance for chaining\n     * @emits change Emitted when any attribute changes. Listeners receive `(model, changedAttributes, ...args)`\n     * @emits change:attribute Emitted for each changed attribute. Listeners receive `(model, newValue, ...args)`\n     * @example\n     * // Basic usage\n     * model.set('name', 'Alice');\n     * model.set({ name : 'Alice', age : 30 });\n     * \n     * // With options for listeners\n     * model.set('name', 'Bob', { silent : false, validate : true });\n     * model.on('change:name', (model, value, options) => {\n     *     if (options?.validate) {\n     *         // Custom validation logic\n     *     }\n     * });\n     */\n    set(key, value, ...rest) {\n        let attrs, args;\n        // Handle both `\"key\", value` and `{key: value}` style arguments.\n        if (typeof key === 'object') {\n            attrs = key;\n            args = [value, ...rest];\n        } else {\n            attrs = { [key] : value };\n            args = rest;\n        }\n        // Are we in a nested `set` call?\n        // Calling a `set` inside a `change:attribute` or `change` event listener\n        const changing = this._changing;\n        this._changing = true;\n        // Store changed attributes.\n        const changed = {};\n        // Store previous attributes.\n        if (!changing) {\n            this.previous = Object.assign({}, this.attributes);\n        }\n        // Set attributes.\n        Object.keys(attrs).forEach(key => {\n            // Use equality to determine if value changed.\n            if (attrs[key] !== this.attributes[key]) {\n                changed[key] = attrs[key];\n                this.attributes[key] = attrs[key];\n            }\n        });\n\n        const changedKeys = Object.keys(changed);\n        // Pending `change` event arguments.\n        if (changedKeys.length) this._pending = ['change', this, changed, ...args];\n        // Emit `change:attribute` events.\n        changedKeys.forEach(key => {\n            this.emit(`change:${key}`, this, attrs[key], ...args);\n        });\n        // Don't emit `change` event until the end of the nested \n        // `set` calls inside `change:attribute` event listeners.\n        if (changing) return this;\n        // Emit `change` events, that might be nested.\n        while (this._pending) {\n            const pendingChange = this._pending;\n            this._pending = null;\n            this.emit.apply(this, pendingChange);\n        }\n        // Reset flags.\n        this._pending = null;\n        this._changing = false;\n\n        return this;\n    }\n\n    /**\n     * Transforms and validates data before it becomes model attributes.\n     * Called during construction with all constructor arguments, allowing flexible data processing.\n     * Override this method to transform incoming data, create nested models, or handle different data formats.\n     * \n     * @param {object} [data={}] Primary data object to be parsed into attributes\n     * @param {...*} [args] Additional arguments from constructor, useful for parsing options\n     * @return {object} Processed data that will become the model's attributes\n     * @example\n     * // Transform nested objects into models\n     * class User extends Model {}\n     * class Order extends Model {\n     *     parse(data, options = {}) {\n     *         // Skip parsing if requested\n     *         if (options.raw) return data;\n     *         // Transform user data into User model\n     *         const parsed = { ...data };\n     *         if (data.user && !(data.user instanceof User)) {\n     *             parsed.user = new User(data.user);\n     *         }\n     *         return parsed;\n     *     }\n     * }\n     * \n     * // Usage with parsing options\n     * const order1 = new Order({ id : 1, user : { name : 'Alice' } }); // user becomes User model\n     * const order2 = new Order({ id : 2, user : { name : 'Bob' } }, { raw : true }); // user stays plain object\n     */\n    parse(data) {\n        return data;\n    }\n\n    /**\n     * Return object representation of the model to be used for JSON serialization.\n     * By default returns a copy of `this.attributes`.\n     * You can override this method to customize serialization behavior, such as calling `toJSON` recursively on nested Model instances.\n     * @return {object} Object representation of the model to be used for JSON serialization.\n     * @example\n     * // Basic usage - returns a copy of model attributes:\n     * const user = new Model({ name : 'Alice', age : 30 });\n     * const json = user.toJSON();\n     * console.log(json); // { name : 'Alice', age : 30 }\n     * \n     * // Override toJSON for recursive serialization of nested models:\n     * class User extends Model {}\n     * class Order extends Model {\n     *     parse(data) {\n     *         // Ensure user is always a User model\n     *         return { ...data, user : data.user instanceof User ? data.user : new User(data.user) };\n     *     }\n     * \n     *     toJSON() {\n     *         const result = {};\n     *         for (const [key, value] of Object.entries(this.attributes)) {\n     *             if (value instanceof Model) {\n     *                 result[key] = value.toJSON();\n     *             } else {\n     *                 result[key] = value;\n     *             }\n     *         }\n     *         return result;\n     *     }\n     * }\n     * const order = new Order({ id : 1, user : { name : 'Alice' } });\n     * const json = order.toJSON();\n     * console.log(json); // { id : 1, user : { name : 'Alice' } }\n     */\n    toJSON() {\n        return Object.assign({}, this.attributes);\n    }\n}\n\n/**\n * Static property that defines a prefix for generated getters/setters.\n * When set, all attribute properties will be prefixed (e.g., 'attr_name' instead of 'name').\n * Useful for avoiding naming conflicts or creating a consistent property naming convention.\n * @type {string}\n * @default ''\n * @example\n * // Set prefix for all models of this class\n * class ApiModel extends Model {\n *     static attributePrefix = 'attr_';\n * }\n * \n * const user = new ApiModel({ name : 'Alice', email : 'alice@example.com' });\n * console.log(user.attr_name);  // 'Alice'\n * console.log(user.attr_email); // 'alice@example.com'\n * \n * // Still access via get/set methods without prefix\n * console.log(user.get('name')); // 'Alice'\n * user.set('name', 'Bob');\n * console.log(user.attr_name); // 'Bob'\n */\nModel.attributePrefix = '';\n"],"names":["getResult"],"mappings":";;;;;;;;;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,MAAM,KAAK,SAAS,OAAO,CAAC;AAC3C,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,EAAE;AACf;AACA,QAAQ,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC;AACjD;AACA,QAAQ,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAEA,eAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC9G;AACA,QAAQ,IAAI,CAAC,QAAQ,GAAG,EAAE;AAC1B;AACA,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7E,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,aAAa,GAAG,CAAC;;AAErB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA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"}