{"id":15371808,"url":"https://github.com/xescugc/jsmoo","last_synced_at":"2025-04-15T14:05:40.951Z","repository":{"id":57284569,"uuid":"49365685","full_name":"xescugc/jsmoo","owner":"xescugc","description":"JavaScript Minimalist Object Orientation","archived":false,"fork":false,"pushed_at":"2016-11-04T14:18:44.000Z","size":97,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-15T14:05:30.581Z","etag":null,"topics":["javascript","moo","moose","oo"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xescugc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-10T12:22:04.000Z","updated_at":"2017-04-04T09:07:21.000Z","dependencies_parsed_at":"2022-09-17T02:12:41.988Z","dependency_job_id":null,"html_url":"https://github.com/xescugc/jsmoo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xescugc%2Fjsmoo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xescugc%2Fjsmoo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xescugc%2Fjsmoo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xescugc%2Fjsmoo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xescugc","download_url":"https://codeload.github.com/xescugc/jsmoo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249085439,"owners_count":21210267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","moo","moose","oo"],"created_at":"2024-10-01T13:48:52.402Z","updated_at":"2025-04-15T14:05:40.929Z","avatar_url":"https://github.com/xescugc.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status][travis-image]][travis-url]\n```\n                         _                           \n                        | |                          \n                        | |___ _ __ ___   ___   ___  \n                    _   | / __| '_ ` _ \\ / _ \\ / _ \\ \n                   | |__| \\__ \\ | | | | | (_) | (_) |\n                    \\____/|___/_| |_| |_|\\___/ \\___/ \n                                                     \n                                                     \n\n```\n  * [Jsmoo](#jsmoo)\n  * [Installation](#installation)\n  * [Simple Example](#simpleexample)\n  * [API](#api)\n    * [beforeInitialize](#beforeinitialize)\n    * [afterInitialize](#afterinitialize)\n    * [before](#before)\n    * [after](#after)\n    * [has](#has)\n      * [is](#is)\n      * [isa](#isa)\n      * [default](#default)\n      * [required](#required)\n      * [lazy](#lazy)\n      * [predicate](#predicate)\n      * [clearer](#clearer)\n      * [builder](#builder)\n      * [trigger](#trigger)\n      * [coerce](#coerce)\n    * [does](#does)\n    * [getAttributes](#getattributes)\n  * [Role](#role)\n\n# Jsmoo\n\nJsmoo (JavaScript Minimalist Object Orientation), it's a library that allows you to define consistent Classes and Roles with a simple API. It's inpired for [Perl][perl] libraries [Moo][moo] and [Moose][moose], and also from [Perl6][perl6]. It provides type validation for attributes (`isa`), presence validation (`required`), defaults (`default`), role composition (`does` and `Role`) and much more!.\n\nIf you want some slaides of a presentation about this lib, here you have the [link][slides]\n\n# Installation\n\nWith npm:\n\n``` js\n  $\u003e npm install --save jsmoo\n```\n\n\u003ca name='simpleexample'\u003e\n\n# Simple Example\n\nWithout Jsmoo:\n\n``` js\n  class Client extends Jsmoo {\n    constructor({name, surname, age = 18}) {\n      if (!name) throw new Error('... some error ...');\n      if (typeof name !== 'string') throw new Error('... some error ...');\n      if (typeof age !== 'number') throw new Error('... some error ...');\n      if (typeof surname !== 'string') throw new Error('... some error ...');\n\n      this.name = name;\n      this.surname = surname;\n      this.age = age;\n    }\n\n    fullName() {\n      return `${this.name} ${this.surname}`;\n    }\n  }\n\n  const client = new Client({name: 'Pepito', surname: 'Grillo'});\n  console.log(client.fullName());\n  //  =\u003e Pepito Grillo\n```\n\nWith Jsmoo:\n\n``` js\n  import Jsmoo from 'jsmoo';\n\n  class Client extends Jsmoo {\n    fullName() {\n      return `${this.name} ${this.surname}`;\n    }\n  }\n\n  // Define the attributes and options\n  Client.has({\n    name:     { is: 'rw', isa: 'string', required: true },\n    surname:  { is: 'rw', isa: 'string' },\n    age:      { is: 'rw', isa: 'number', default: 18 },\n  });\n\n  const client = new Client({name: 'Pepito', surname: 'Grillo'});\n  console.log(client.fullName());\n  //  =\u003e 'Pepito Grillo'\n```\n\nThe example without Jsmoo it's not the same of the one with Jsmoo, because to write the access validation `is` is so much code :) but you get the point, no?\n\n# API\n\nThe module itself exports more than one module:\n\n``` js\n  import Jsmoo, { Role, before, after } from 'jsmoo';\n```\n\nThe way the Classes are initialized is with a plain Object, where the keys are the attributes defined on the `has`.\n\n## beforeInitialize\n\nIf you define this function on you class, will be called before the initialization arguments are passed to the constructor, here you can redefine this arguements as you want, the `return` from this function will be the ones the constructor will use to initialize the object.\n\n_Example:_\n\n``` js\n  class File extends from Jsmoo {\n    beforeInitialize(args) {\n      if (!args.extension) {\n        args.extension = args.filename.split('.')[-1];\n      }\n      return args;\n    }\n  }\n\n  File.has({\n    extension:  { is: 'ro', isa: 'string', required: true },\n    filename:   { is: 'ro', isa: 'string', required: true },\n  });\n\n  const file = new File({filename: 'photo.jpg'});\n  console.log(file.extension);\n  //  =\u003e 'jpg'\n```\n\n## afterInitialize\n\nIf you define this function on you class, will be called after the initialization without any arguments here you have access to the `this` of the Class.\n\n_Example:_\n\n``` js\n  class File extends from Jsmoo {\n    afterInitialize() {\n      console.log(this.filename);\n    }\n  }\n\n  File.has({\n    filename:   { is: 'ro', isa: 'string', required: true },\n  });\n\n  const file = new File({filename: 'photo.jpg'});\n  //  =\u003e 'photo.jpg'\n```\n\nYou can use this function to register some callback or validation.\n\n## before\n\n_API:_ before(rootObject, beforeThis, beforeFunction)\n\nThe `before` function is called before the specified function. The result of it is totally ignored, but you can throw an error to stop the execution if you need too.\n\n``` js\n  import Jsmoo, { before } from 'jsmoo';\n\n  class Client extends from Jsmoo {\n    save() {\n      // Save the client (fake)\n      db.insert(this);\n    }\n  }\n\n  Client.has({\n    name:     { is: 'rw', isa: 'string', predicate: 1},\n    surname:  { is: 'rw', isa: 'string', predicate: 1},\n  });\n\n  before(Client.prototype, 'save', function() {\n    if (this.hasSurname() \u0026\u0026 !this.hasName()) {\n      throw new TypeError('Need name if surname');\n    }\n  });\n\n  const client = new Client({ surname: 'Grillo' });\n\n  client.save();\n```\n\nIt's really useful to perform validations/callbacks, for example a _before save_ will do something before calling the save function.\n\n## after\n\nThe `after` function is called after the specified function, the result of it is totally ignored.\n\n``` js\n  import Jsmoo, { after } from 'jsmoo';\n\n  class Client extends from Jsmoo {\n    save() {\n      // Save the client (fake)\n      db.insert(this);\n    }\n  }\n\n  Client.has({\n    name:     { is: 'rw', isa: 'string' },\n  });\n\n  after(Client.prototype, 'save', function() {\n    Mailer.send(this)\n  });\n\n  const client = new Client({ surname: 'Grillo' });\n\n  client.save();\n```\n\nIt's really useful to perform validations/callbacks, for example a _after create_ will do something after the function create is called.\n\n## has\n\nHas provides the core functionallity of this module, define the attributes of the Class as easy as possible as clear as possible. This method is a `static` method of the Class that has extended from `Jsmoo`.\n\nIt expects a Object as parameters and each key of this object will become an attribute of the class. The configuration of the attribute is the value of the attribute key.\n\n__Example:__\n\n``` js\n  class File extends from Jsmoo { }\n\n  File.has({\n    filename: { is: 'ro' }\n  });\n\n```\n\nThis is the most basic configuration, the attributes `filename` and his configuration `{ is: 'ro'}`.\n\n### is\n\nIt defines the accesability of the attribute, it's the only configuration __REQUIRED__ on the attribute, it can have the following values:\n\n  * `rw`: The attribute can be setted with new values (__Read Write__)\n  * `ro`: You can not change the value of this attribute (__Read Only__)\n\nIf you try to change a `ro` attribute it will raise an error.\n\n### isa\n\nIt defines the type of the attribute, it can have the following values:\n\n  * `string` or `String`\n  * `number` or `Number`\n  * `array` or `Array`\n  * `boolean` or `Boolean`\n  * `object` or `Object`\n  * `Maybe[type]` validates the type but it wont throw error if it's `undefined` or `null`\n  * Your types\n  * Custom validations\n\nEach of this types is defined as string on the `isa` except for the 'Custom validations' which are functions that validates the value. For custom validations each time a value es setted to the attribute it'll run this validations, the result of those is ignored, the only way to stop the execution is to throw an error.\n\n__Example:__\n\n``` js\n  class Client extends Jsmoo { }\n\n  function isEven(value) {\n    if (value % 2 !== 0) throw new Error('Not even value')\n  }\n\n  Client.has({\n    name:     { is: 'rw', isa: 'string' },\n    age:      { is: 'rw', isa: 'number' },\n    address:  { is: 'rw', isa: 'object' },\n    valid:    { is: 'rw', isa: 'boolean'},\n    city:     { is: 'rw', isa: 'City' }, // Your types\n    even:     { is: 'rw', isa: isEven }, // Your custom validation\n    number:   { is: 'rw', isa: 'Maybe[number]' }, // Can be undefined or null\n  });\n\n  const city = new City();\n\n  const client = new Client({\n    name: 'Pepito',\n    age: 45,\n    address: {},\n    valid: true,\n    city: city,\n    even: 2,\n  });\n```\n\n### default\n\nIt defines a default value of an attribute _only_ if no one is given in the initialization, it can be a simple value or a function, the function has the `this` context of the Class but if you try to access some attribute that it's also default, you may, or may not, get the value you expect, if you want this behavior you shoud define the attributte you want to access as [lazy](#lazy).\n\n__Example:__\n\n``` js\n  class Client extends Jsmoo {}\n\n  Client.has({\n    email:    { is: 'rw' }\n    name:     { is: 'rw', default() { return this.email.split('@')[0] },\n    valid:    { is: 'rw', isa: 'boolean', default: true },\n    created:  { is: 'rw', default() { return new Date }},\n  });\n\n  const client = new Client({\n    email: 'pepitogrillo@gmail.com'\n  });\n\n  client.name     // pepitogrillo\n  client.valid    // true\n  client.created  // Date\n\n```\n\n### required\n\nIt describes the attribute as `required` as a boolean value, which means that it must be (if true) one of the parameters on initialization time, if it's not present it will fail loudly.\n\n__Example:__\n\n``` js\n  class Client extends Jsmoo {}\n\n  Client.has({\n    name: { is: 'rw', required: true }\n  })\n```\n\n### lazy\n\nThe attributes defined as `lazy` will be instanciated only when the attribute is called.\n\n__Example:__\n\n```js\n  class Client extends Jsmoo {}\n\n  Clint.has({\n    name: { is: 'rw', lazy: true }\n  });\n```\n\nThis is useful in combination with [default](#default) or [builder](#builder) because you can use it to catch heaby operations like DB queryies.\n\n### predicate\n\nCreated a function (`has${attributeName}` if it start with _ then `_has${attributeName}`) to validate if the value is defined, wich means the values is not `undefined` or `null`\n\n__Example:__\n\n```js\n  class Client extends Jsmoo {}\n\n  Clint.has({\n    name: { is: 'rw', predicate: true }\n  });\n\n  let obj = new Client({ name: 'value' });\n  obj.hasName()\n  // =\u003e true\n  obj.name = undefined;\n  obj.hasName()\n  // =\u003e false\n```\n### clearer\n\nCreated a function (`clear${attributeName}` if it start with _ then `_clear${attributeName}`) to clear the value, which means removing the attribute from the internal store.\n\n__Example:__\n\n```js\n  class Client extends Jsmoo {}\n\n  Clint.has({\n    name: { is: 'rw', clearer: true }\n  });\n\n  let obj = new Client({ name: 'value' });\n  obj.name\n  // =\u003e value\n  obj.clearName();\n  obj.name\n  // =\u003e undefined\n```\n\n### builder\n\nDefines a function to build the attribute if not initialized, if it has a Boolean value it will call the function `build${attributeName}` (if it start with _ then `_build${attributeName}`) but you can override this by passing a string with the name of the builder function that you want, this function would have the `this` context of the class.\n\n__Example:__\n\n```js\n  class Client extends Jsmoo {}\n\n  Clint.has({\n    name: { is: 'rw', builder: true },\n    age:  { is: 'rw', builder: 'buildAgeForUser' },\n  });\n\n  Client.prototype.buildAgeForUser = function() {}\n  Client.prototype.buildName = function() {}\n\n```\n\nThis is very useful to use it with Role compositions and the role defined the builder and then the Ojbect with the Role has to define the custom implementation.\n\n### trigger\n\n_API:_ function(newValue, oldValue) {}\n\nIt creates a handle that will trigger after the attribute is setted. This includes the constructor but not [`default`](#default) ond [`builder`](#builder). This handle will recieve the `oldValue` and the `newValue`. It can be defined with a boolean value, in which case would call a function with the name of the attribute like this `trigger${attributeName}` (if is starts with _ then `_trigger${attributeName}`. Or it can be defined with a funciton.\n\n__Example:__\n\n```js\n  class Client extends Jsmoo {}\n\n  Client.has({\n    name:     { is: 'rw', trigger: 1 },\n    age:      { is: 'rw', trigger: triggerForAge },\n    surname:  { is: 'rw', trigger: trigger(newValue, oldValue) {} },\n  });\n\n  Client.prototype.triggerName = function (newValue, oldValue) { }\n  function triggerForAge (newValue, oldValue) { }\n```\n\nVery useful to register some kind of callbacks to some attributes after they change.\n\n### coerce\n\n_API:_ function(value) {}\n\nIt takes a function and coerce the attribute. Which means it may transform the value to another one.\n\n__Example:__\n\n```js\n  class Client extends Jsmoo {}\n\n  function stringToNumber(value) {\n      if (typeof value === 'string') {\n        return value * 1;\n      }\n      return value\n  }\n\n  Client.has({\n    age:    { is: 'rw', isa: 'number', coerce: stringToNumber },\n  });\n\n  const client = new Client({ age: '25' });\n  client.age\n  # 18\n  typeof client.age\n  # number\n\n```\n\nIt's usefull to transform no objects to objects, or different types (string =\u003e integer)\n\n## does\n\nIt's the way to acomplish composition, there are some rules for Role composition:\n\n  * Only `Roles` can be composed.\n  * Roles can `override` existing attributes with the `+` sign.\n  * Classes can `override` existing attributes with the sign `+` sign.\n  * If one of the _overrided_ attributes is not declated (with has) before the declaration of the _override_ it will fail loudly, basically if you try to `+name` and `name` is not defiend by the Role then it fails..\n  * If a function is defined in the main Class, the Role will not _override_ it.\n\nThe instance and class functions will be composed to the main Class and also the attributes defined with [`has`](#has) on the Role.\n\n__Example:__\n\n``` js\n  //------- address_role.js\n  import Jsmoo, { Role } from 'jsmoo';\n\n  class AddressRole extends Role {\n    static staticFunction() {\n      return 'static'\n    }\n    instanceFunction() {\n      return this.name\n    }\n  }\n\n  AddressRole.has({\n    address: { is: 'rw', default: 'C/ To Pepi' }\n  })\n\n  export default AddressRole;\n\n  //------- person.js\n  import Jsmoo, { Role } from 'jsmoo';\n  import AddressRole from './address_role';\n\n  class Person extends Jsmoo {}\n\n  Person.does(AddressRole)\n\n  Person.has({\n    name:       { is: 'rw' },\n    '+address': { default: 'C/ Pepi To' },\n  })\n\n  Person.staticFunction()\n  // =\u003e 'static'\n\n  let person = new Person({ name: 'Pepito' })\n\n  person.instanceFunction()\n  // =\u003e 'Pepito'\n\n  person.address\n  // =\u003e 'C/ Pepi To'\n```\n\n## getAttributes\n\nThis function is present in all the Classes extending of Jsmoo as a instance function, it returns all the attributes setted with [`has`](#has) of the Class:\n\n__Example;__\n\n``` js\n\n  class Client extends Jsmoo {}\n\n  Client.has({\n    name: { is: 'rw' },\n    age:  { is: 'rw', default: 18 },\n  });\n\n  const client = new Client({\n    name: 'Pepito'\n  });\n\n  console.log(client.getAttribute());\n\n  // =\u003e { name: 'Pepito', age:  18 }\n\n```\n\n# Role\n\nRoles are the way to achive composition, they are similar to the Jsmoo class but with some differences:\n\n  * They are the only ones that can be composed with `does`.\n  * Roles can not be initialized.\n\nRoles also have the [`has`](#has) static function to define attributes, wich then will be extended to the main Jsmoo Class.\n\nThey work the same way of a Jsmoo Class.\n\n``` js\nimport Jsmoo, { Role } from 'jsmoo'\n\nclass Document extends Role {}\n\nDocument.has({\n  _id: { is: 'rw', isa: 'number' }\n})\n\nclass Person extends Jsmoo {}\n\nPerson.with(Document)\n\nconst person = new Person({ _id: 23 })\n\nconsole.log(person._id)\n// =\u003e 23\n```\n\nThe use of Role is up to you hehe, basically is to abstract some code that is used in other classes, like the logic to query or serialize to a DB (like a ORM)\n\n[moo]: https://metacpan.org/pod/Moo\n[moose]: https://metacpan.org/pod/Moose\n[travis-image]: https://travis-ci.org/XescuGC/jsmoo.svg?branch=master\n[travis-url]: https://travis-ci.org/XescuGC/jsmoo\n[perl]: https://www.perl.org/\n[perl6]: https://perl6.org/\n[slides]: https://github.com/XescuGC/jsmoo_presentation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxescugc%2Fjsmoo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxescugc%2Fjsmoo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxescugc%2Fjsmoo/lists"}