{"id":21424145,"url":"https://github.com/linhntaim/magic-class","last_synced_at":"2026-04-08T16:01:27.845Z","repository":{"id":204185429,"uuid":"711083457","full_name":"linhntaim/magic-class","owner":"linhntaim","description":"Activate PHP-like magic methods in Javascript classes and instances.","archived":false,"fork":false,"pushed_at":"2023-11-07T03:14:36.000Z","size":296,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-24T22:56:17.579Z","etag":null,"topics":["magic","magic-method","magic-methods","method","methods","node","node-js","node-package","nodejs","npm","php","php-like"],"latest_commit_sha":null,"homepage":"","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/linhntaim.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-28T06:55:25.000Z","updated_at":"2023-10-29T07:06:47.000Z","dependencies_parsed_at":"2023-11-06T09:41:09.681Z","dependency_job_id":"f0d8ea04-6cae-4478-a1c2-37fc30bcd90f","html_url":"https://github.com/linhntaim/magic-class","commit_stats":null,"previous_names":["linhntaim/magic-class"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/linhntaim/magic-class","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linhntaim%2Fmagic-class","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linhntaim%2Fmagic-class/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linhntaim%2Fmagic-class/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linhntaim%2Fmagic-class/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/linhntaim","download_url":"https://codeload.github.com/linhntaim/magic-class/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linhntaim%2Fmagic-class/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31562696,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["magic","magic-method","magic-methods","method","methods","node","node-js","node-package","nodejs","npm","php","php-like"],"created_at":"2024-11-22T21:19:53.286Z","updated_at":"2026-04-08T16:01:27.817Z","avatar_url":"https://github.com/linhntaim.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# magic-class\n\n[![NPM version](https://img.shields.io/npm/v/magic-class.svg?style=flat-square)](https://www.npmjs.com/package/magic-class)\n[![Github Actions](https://img.shields.io/github/actions/workflow/status/linhntaim/magic-class/build.yml?style=flat-square)](https://github.com/linhntaim/magic-class/actions/workflows/build.yml)\n[![Coveralls](https://img.shields.io/coveralls/github/linhntaim/magic-class?style=flat-square)](https://coveralls.io/github/linhntaim/magic-class)\n[![License](https://img.shields.io/npm/l/magic-class?style=flat-square)](https://github.com/linhntaim/magic-class/blob/master/LICENSE)\n\nActivate PHP-like magic methods in Javascript classes and instances.\n\n---\n\n- [Installation](#installation)\n- [Usage](#usage)\n- [Features](#features)\n    - [Magic methods](#magic-methods)\n        - [`__set`](#__set)\n        - [`__get`](#__get)\n        - [`__call`](#__call)\n        - [`__invoke`](#__invoke)\n        - [`__has`](#__has)\n        - [`__delete`](#__delete)\n        - [Method chaining](#method-chaining)\n    - [Magic static methods](#magic-static-methods)\n        - [Static `__set`](#static-__set)\n        - [Static `__get`](#static-__get)\n        - [Static `__call`](#static-__call)\n        - [Static `__has`](#static-__has)\n        - [Static `__delete`](#static-__delete)\n        - [Static method chaining](#static-method-chaining)\n    - [Use `Symbol` as magic method name](#use-symbol-as-magic-method-name)\n    - [Prototype operations](#prototype-operations)\n    - [Strict mode](#strict-mode)\n    - [Special magic static methods](#special-magic-static-methods)\n        - [Static `__instance`](#static-__instance)\n        - [Static `__singleton`](#static-__singleton)\n    - [Inheritance](#inheritance)\n\n---\n\n## Installation\n\n```bash\nnpm install magic-class --save\n```\n\n## Usage\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static magicProps = {}\n\n    static __set(prop, value) {\n        this.magicProps[`static:${prop}`] = value\n    }\n\n    static __get(prop) {\n        if (`static:${prop}` in this.magicProps) {\n            return this.magicProps[`static:${prop}`]\n        }\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return `static:${prop}`\n    }\n\n    static __call(method, ...parameters) {\n        return {method: `static:${method}`, parameters}\n    }\n\n    static __has(prop) {\n        return `static:${prop}` in this.magicProps\n    }\n\n    static __delete(prop) {\n        return delete this.magicProps[`static:${prop}`]\n    }\n\n    magicProps = {}\n\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __set(prop, value) {\n        this.magicProps[prop] = value\n    }\n\n    __get(prop) {\n        if (prop in this.magicProps) {\n            return this.magicProps[prop]\n        }\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return prop\n    }\n\n    __call(method, ...parameters) {\n        return {method, parameters}\n    }\n\n    __has(prop) {\n        return prop in this.magicProps\n    }\n\n    __delete(prop) {\n        return delete this.magicProps[prop]\n    }\n\n    __invoke(...parameters) {\n        return {parameters}\n    }\n}\n\n// Create magic class\nconst MagicClass = magic(NormalClass)\n// magic static __set\nMagicClass.magic = true\nconsole.log(MagicClass.magicProps)          // (object) {'static:magic': true}\n// magic static __get\nconsole.log(MagicClass.magic)               // (boolean) true\nconsole.log(MagicClass.any)                 // (string) 'static:any'\n// magic static __call\nconsole.log(MagicClass.callAny(true))       // (object) {method: 'static:callAny', parameters: [true]}\n// magic static __has\nconsole.log('magic' in MagicClass)          // (boolean) true\n// magic static __delete\nconsole.log(delete MagicClass.magic)        // (boolean) true\nconsole.log('magic' in MagicClass)          // (boolean) false\n\n// Create magic instance\nconst magicInstance = new MagicClass('normal')\n/* or */\n// const magicInstance = magic(new NormalClass())\n// magic __set\nmagicInstance.magic = true\nconsole.log(magicInstance.magicProps)       // (object) {magic: true}\n// magic __get\nconsole.log(magicInstance.magic)            // (boolean) true\nconsole.log(magicInstance.any)              // (string) 'any'\n// magic __call\nconsole.log(magicInstance.callAny(true))    // (object) {method: 'callAny', parameters: [true]}\n// magic __has\nconsole.log('magic' in magicInstance)       // (boolean) true\n// magic __delete\nconsole.log(delete magicInstance.magic)     // (boolean) true\nconsole.log('magic' in magicInstance)       // (boolean) false\n// magic __invoke\nconsole.log(magicInstance(true))            // (object) {parameters: [true]}\n```\n\n## Features\n\n### Magic methods\n\n#### `__set`\n\n`__set` is run when writing data to non-existing properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    magicProps = {}\n\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __set(prop, value) {\n        this.magicProps[prop] = value\n    }\n}\n\nconst MagicClass = magic(NormalClass)\nconst magicInstance = new MagicClass('normal')\n\n// existing prop\nconsole.log(magicInstance.normal)       // (string) 'normal'\nmagicInstance.normal = 'new value'\nconsole.log(magicInstance.normal)       // (string) 'new value'\n// non-existing prop\ntry {\n    console.log(magicInstance.magic)\n}\ncatch (e) {\n    console.log(e.message)              // (string) 'Property [magic] does not exist.'\n}\nmagicInstance.magic = true\ntry {\n    console.log(magicInstance.magic)\n}\ncatch (e) {\n    console.log(e.message)              // (string) 'Property [magic] does not exist.'\n}\nconsole.log(magicInstance.magicProps)   // (object) {magic: true}\n```\n\n***Note:*\n\n- While magic is activated in default [strict mode](#strict-mode)\n  and without magic `__get`/`__call` methods, accessing non-existing properties\n  will throw `ReferenceError` exception instead of getting `undefined`.\n- While magic is activated in default [strict mode](#strict-mode),\n  writing data to non-existing properties will throw `ReferenceError` exception.\n\n#### `__get`\n\n`__get` is run when reading data from non-existing properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return `magic:${prop}`\n    }\n}\n\nconst MagicClass = magic(NormalClass)\nconst magicInstance = new MagicClass('normal')\n\n// existing prop\nconsole.log(magicInstance.normal)       // (string) 'normal'\n// non-existing prop\nconsole.log(magicInstance.value)        // (string) 'magic:value'\nconsole.log(magicInstance.any)          // (string) 'magic:any'\ntry {\n    console.log(magicInstance.callAny)\n}\ncatch (e) {\n    console.log(e.message)              // (string) 'Property [callAny] does not exist.'\n}\n```\n\n***Note:* While magic is activated in default [strict mode](#strict-mode) and\nwithout magic `__call` method, accessing non-existing properties\nwill throw `ReferenceError` exception when magic `__get` returns `undefined`.\n\n#### `__call`\n\n`__call` is run when calling non-existing properties as function\nwhile magic `__get` is not declared or magic `__get` returns `undefined`.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\n/* magic `__get` is not declared */\nclass NormalClass1\n{\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __call(method, ...parameters) {\n        return {method, parameters}\n    }\n}\n\nconst MagicClass1 = magic(NormalClass1)\nconst magicInstance1 = new MagicClass1('normal')\n\n// existing prop\nconsole.log(magicInstance1.normal)       // (string) 'normal'\n// non-existing prop\nconsole.log(magicInstance1.any)          // (function)\nconsole.log(magicInstance1.value(1))     // (object) {method: 'value', parameters: [1]}\n\n/* magic `__get` returns `undefined` in some cases */\nclass NormalClass2\n{\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return `magic:${prop}`\n    }\n\n    __call(method, ...parameters) {\n        return {method, parameters}\n    }\n}\n\nconst MagicClass2 = magic(NormalClass2)\nconst magicInstance2 = new MagicClass2('normal')\n\n// existing prop\nconsole.log(magicInstance2.normal)          // (string) 'normal'\n// non-existing prop\nconsole.log(magicInstance2.any)             // (string) 'magic:any'\ntry {\n    console.log(magicInstance2.value(1))\n}\ncatch (e) {\n    console.log(e.message)                  // (string) 'magicInstance2.value is not a function'\n}\nconsole.log(magicInstance2.callValue(1))    // (object) {method: 'callValue', parameters: [1]}\n```\n\n***Note:* If magic `__get` never returns `undefined`, magic `__call` is also never run.\n\n#### `__invoke`\n\n`__invoke` is run when calling instance as a function.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __invoke(parameters) {\n        return {parameters}\n    }\n}\n\nconst MagicClass = magic(NormalClass)\nconst magicInstance = new MagicClass('normal')\n\nconsole.log(magicInstance(1))   // (object) {parameters: [1]}\n```\n\n#### `__has`\n\n`__has` is run when checking existence of non-existing properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __has(prop) {\n        if (prop === 'magic') {\n            return true\n        }\n        return false\n        /* or */\n        // return // returning nothing means returning `false`\n    }\n}\n\nconst MagicClass = magic(NormalClass)\nconst magicInstance = new MagicClass('normal')\n\n// existing prop\nconsole.log('normal' in magicInstance)      // (boolean) true\n// non-existing prop\nconsole.log('magic' in magicInstance)       // (boolean) true\nconsole.log('other' in magicInstance)       // (boolean) false\n```\n\n***Note:* Magic `__has` has a fallback of returning `false` in case it returns nothing.\n\n#### `__delete`\n\n`__delete` is run when deleting non-existing properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    magicProps = {\n        magic: true,\n    }\n\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    __delete(prop) {\n        return delete this.magicProps[prop]\n    }\n}\n\nconst MagicClass = magic(NormalClass)\nconst magicInstance = new MagicClass('normal')\n\n// existing prop\ntry {\n    console.log(delete magicInstance.normal)\n}\ncatch (e) {\n    console.log(e.message)                          // (string) 'Cannot delete property [normal].'\n}\n// non-existing props\nconsole.log('magic' in magicInstance.magicProps)    // (boolean) true\nconsole.log(delete magicInstance.magic)             // (boolean) true\nconsole.log('magic' in magicInstance.magicProps)    // (boolean) false\nconsole.log('other' in magicInstance.magicProps)    // (boolean) false\nconsole.log(delete magicInstance.other)             // (boolean) true\nconsole.log('other' in magicInstance.magicProps)    // (boolean) false\n```\n\n***Note:*\n\n- While magic is activated in default [strict mode](#strict-mode), deleting existing properties\n  throws `TypeError` exception.\n- Magic `__delete` has a fallback of returning `true` in case it returns nothing.\n\n#### Method chaining\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    chain = []\n\n    constructor(...parameters) {\n        this.push(...parameters.map(p =\u003e `construct:${p}`))\n    }\n\n    push(...parameters) {\n        this.chain.push(...parameters)\n        return this\n    }\n\n    __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        if (prop === 'self') {\n            return this\n        }\n        return this.push(`get:${prop}`)\n    }\n\n    __call(method, ...parameters) {\n        if (['callInsert', 'callAdd'].includes(method)) {\n            return this.push(...parameters.map(p =\u003e `${method}:${p}`))\n        }\n        return this.push(`call:${method}`)\n    }\n\n    __invoke(...parameters) {\n        return this.push(...parameters.map(p =\u003e `invoke:${p}`))\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n// Chain: (constructor)-\u003e(magic __invoke)-\u003e(existing method)-\u003e(magic __call)-\u003e(magic __get)-\u003e(existing prop)\nconst magicChain = (new MagicClass(0))(1).push(2).callInsert(3).callAdd(4).callAny(5).any.self.chain\nconsole.log(magicChain) // (array) ['construct:0', 'invoke:1', 2, 'callInsert:3', 'callAdd:4', 'call:callAny', 'get:any']\n```\n\n### Magic static methods\n\n#### Static `__set`\n\nStatic `__set` is run when writing data to non-existing static properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static normal = 'normal'\n\n    static magicProps = {}\n\n    static __set(prop, value) {\n        this.magicProps[prop] = value\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n// existing static prop\nconsole.log(MagicClass.normal)      // (string) 'normal'\nMagicClass.normal = 'new value'\nconsole.log(MagicClass.normal)      // (string) 'new value'\n// non-existing static prop\ntry {\n    console.log(MagicClass.magic)\n}\ncatch (e) {\n    console.log(e.message)          // (string) 'Static property [magic] does not exist.'\n}\nMagicClass.magic = true\ntry {\n    console.log(MagicClass.magic)\n}\ncatch (e) {\n    console.log(e.message)          // (string) 'Static property [magic] does not exist.'\n}\nconsole.log(MagicClass.magicProps)  // (object) {magic: true}\n```\n\n***Note:*\n\n- While magic is activated in default [strict mode](#strict-mode) and\n  without magic static `__get`/`__call` methods, accessing non-existing static properties\n  will throw `ReferenceError` exception instead of getting `undefined`.\n- While magic is activated in default [strict mode](#strict-mode),\n  writing data to non-existing static properties will throw `ReferenceError` exception.\n\n#### Static `__get`\n\nStatic `__get` is run when reading data from non-existing static properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static normal = 'normal'\n\n    static __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return `magic:${prop}`\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n// existing static prop\nconsole.log(MagicClass.normal)      // (string) 'normal'\n// non-existing static props\nconsole.log(MagicClass.value)       // (string) 'magic:value'\nconsole.log(MagicClass.any)         // (string) 'magic:any'\ntry {\n    console.log(MagicClass.callAny)\n}\ncatch (e) {\n    console.log(e.message)          // (string) 'Static property [callAny] does not exist.'\n}\n```\n\n***Note:* While magic is activated in default [strict mode](#strict-mode) and\nwithout magic static `__call` method, accessing non-existing static properties\nwill throw `ReferenceError` exception when magic static `__get` returns `undefined`.\n\n#### Static `__call`\n\nStatic `__call` is run when calling non-existing static properties as function\nwhile magic static `__get` is not declared or magic static `__get` returns `undefined`.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\n/* magic `__get` is not declared */\nclass NormalClass1\n{\n    static normal = 'normal'\n\n    static __call(method, ...parameters) {\n        return {method, parameters}\n    }\n}\n\nconst MagicClass1 = magic(NormalClass1)\n\n// existing prop\nconsole.log(MagicClass1.normal)         // (string) 'normal'\n// non-existing prop\nconsole.log(MagicClass1.any)            // (function)\nconsole.log(MagicClass1.value(1))       // (object) {method: 'value', parameters: [1]}\n\n/* magic `__get` returns `undefined` in some cases */\nclass NormalClass2\n{\n    static normal = 'normal'\n\n    static __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return `magic:${prop}`\n    }\n\n    static __call(method, ...parameters) {\n        return {method, parameters}\n    }\n}\n\nconst MagicClass2 = magic(NormalClass2)\n\n// existing static prop\nconsole.log(MagicClass2.normal)         // (string) 'normal'\n// non-existing static props\nconsole.log(MagicClass2.any)            // (string) 'magic:any'\ntry {\n    console.log(MagicClass2.value(1))\n}\ncatch (e) {\n    console.log(e.message)              // (string) 'MagicClass2.value is not a function'\n}\nconsole.log(MagicClass2.callValue(1))   // (object) {method: 'callValue', parameters: [1]}\n```\n\n***Note:* If magic static `__get` never returns `undefined`, magic static `__call` is also never run.\n\n#### Static `__has`\n\nStatic `__has` is run when checking existence of non-existing static properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static normal = 'normal'\n\n    static __has(prop) {\n        if (prop === 'magic') {\n            return true\n        }\n        return false\n        /* or */\n        // return // returning nothing means returning `false`\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n// existing static prop\nconsole.log('normal' in MagicClass)     // (boolean) true\n// non-existing static props\nconsole.log('magic' in MagicClass)      // (boolean) true\nconsole.log('other' in MagicClass)      // (boolean) false\n```\n\n***Note:* Magic static `__has` has a fallback of returning `false` in case it returns nothing.\n\n#### Static `__delete`\n\nStatic `__delete` is run when deleting non-existing static properties.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static normal = 'normal'\n\n    static magicProps = {\n        magic: true,\n    }\n\n    static __delete(prop) {\n        return delete this.magicProps[prop]\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n// existing static prop\ntry {\n    console.log(delete MagicClass.normal)\n}\ncatch (e) {\n    console.log(e.message)                      // (string) 'Cannot delete property [normal].'\n}\n// non-existing static props\nconsole.log('magic' in MagicClass.magicProps)   // (boolean) true\nconsole.log(delete MagicClass.magic)            // (boolean) true\nconsole.log('magic' in MagicClass.magicProps)   // (boolean) false\nconsole.log('other' in MagicClass.magicProps)   // (boolean) false\nconsole.log(delete MagicClass.other)            // (boolean) true\nconsole.log('other' in MagicClass.magicProps)   // (boolean) false\n```\n\n***Note:*\n\n- While magic is activated in default [strict mode](#strict-mode),\n  deleting existing static properties throws `TypeError` exception.\n- Magic static `__delete` has a fallback of returning `true` in case it returns nothing.\n\n#### Static method chaining\n\nIt is possible to call magic static methods in a chain.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static chain = [0]\n\n    static push(...parameters) {\n        this.chain.push(...parameters)\n        return this\n    }\n\n    static __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        if (prop === 'self') {\n            return this\n        }\n        return this.push(`get:${prop}`)\n    }\n\n    static __call(method, ...parameters) {\n        if (['callInsert', 'callAdd'].includes(method)) {\n            return this.push(...parameters.map(p =\u003e `${method}:${p}`))\n        }\n        return this.push(`call:${method}`)\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n// Chain: (class)-\u003e(existing static method)-\u003e(magic static __call)-\u003e(magic static __get)-\u003e(existing static prop)\nconst magicChain = MagicClass.push(1).callInsert(2).callAdd(3).callAny(4).any.self.chain\nconsole.log(magicChain) // (array) [0, 1, 'callInsert:2', 'callAdd:3', 'call:callAny', 'get:any']\n```\n\n### Use `Symbol` as magic method name\n\nBesides strings, there are defined symbols you can use to naming the magic methods:\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\n// Defined symbols\nconsole.log(magic.__set)                    // (symbol) Symbol(Symbol.__set)\nconsole.log(magic.__get)                    // (symbol) Symbol(Symbol.__get)\nconsole.log(magic.__call)                   // (symbol) Symbol(Symbol.__call)\nconsole.log(magic.__has)                    // (symbol) Symbol(Symbol.__has)\nconsole.log(magic.__delete)                 // (symbol) Symbol(Symbol.__delete)\nconsole.log(magic.__invoke)                 // (symbol) Symbol(Symbol.__invoke)\n\nclass NormalClass\n{\n    static magicProps = {}\n\n    // equivalent to `static __set`\n    static [magic.__set](prop, value) {\n        this.magicProps[`static:${prop}`] = value\n    }\n\n    // equivalent to `static __get`\n    static [magic.__get](prop) {\n        if (`static:${prop}` in this.magicProps) {\n            return this.magicProps[`static:${prop}`]\n        }\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return `static:${prop}`\n    }\n\n    // equivalent to `static __call`\n    static [magic.__call](method, ...parameters) {\n        return {method: `static:${method}`, parameters}\n    }\n\n    // equivalent to `static __has`\n    static [magic.__has](prop) {\n        return `static:${prop}` in this.magicProps\n    }\n\n    // equivalent to `static __delete`\n    static [magic.__delete](prop) {\n        return delete this.magicProps[`static:${prop}`]\n    }\n\n    magicProps = {}\n\n    constructor(normal) {\n        this.normal = normal\n    }\n\n    // equivalent to `__set`\n    [magic.__set](prop, value) {\n        this.magicProps[prop] = value\n    }\n\n    // equivalent to `__get`\n    [magic.__get](prop) {\n        if (prop in this.magicProps) {\n            return this.magicProps[prop]\n        }\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return prop\n    }\n\n    // equivalent to `__call`\n    [magic.__call](method, ...parameters) {\n        return {method, parameters}\n    }\n\n    // equivalent to `__has`\n    [magic.__has](prop) {\n        return prop in this.magicProps\n    }\n\n    // equivalent to `__delete`\n    [magic.__delete](prop) {\n        return delete this.magicProps[prop]\n    }\n\n    // equivalent to `__invoke`\n    [magic.__invoke](...parameters) {\n        return {parameters}\n    }\n}\n\n// Create magic class\nconst MagicClass = magic(NormalClass)\n// magic static __set\nMagicClass.magic = true\nconsole.log(MagicClass.magicProps)          // (object) {'static:magic': true}\n// magic static __get\nconsole.log(MagicClass.magic)               // (boolean) true\nconsole.log(MagicClass.any)                 // (string) 'static:any'\n// magic static __call\nconsole.log(MagicClass.callAny(true))       // (object) {method: 'static:callAny', parameters: [true]}\n// magic static __has\nconsole.log('magic' in MagicClass)          // (boolean) true\n// magic static __delete\nconsole.log(delete MagicClass.magic)        // (boolean) true\nconsole.log('magic' in MagicClass)          // (boolean) false\n\n// Create magic instance\nconst magicInstance = new MagicClass('normal')\n/* or */\n// const magicInstance = magic(new NormalClass())\n// magic __set\nmagicInstance.magic = true\nconsole.log(magicInstance.magicProps)       // (object) {magic: true}\n// magic __get\nconsole.log(magicInstance.magic)            // (boolean) true\nconsole.log(magicInstance.any)              // (string) 'any'\n// magic __call\nconsole.log(magicInstance.callAny(true))    // (object) {method: 'callAny', parameters: [true]}\n// magic __has\nconsole.log('magic' in magicInstance)       // (boolean) true\n// magic __delete\nconsole.log(delete magicInstance.magic)     // (boolean) true\nconsole.log('magic' in magicInstance)       // (boolean) false\n// magic __invoke\nconsole.log(magicInstance(true))            // (object) {parameters: [true]}\n```\n\n***Note*: The `Symbol`-naming magic method has a higher priority in calling\nthan the `string`-naming one.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    // equivalent to `__get` but has a higher priority\n    [magic.__get](prop) {\n        return `symbol:${prop}`\n    }\n\n    __get(prop) {\n        return prop\n    }\n}\n\nconst magicInstance = magic(new NormalClass())\nconsole.log(magicInstance.magic) // (string) 'symbol:magic'\n```\n\n### Prototype operations\n\nTechnically, **the class after the magic is activated (which is a proxy object)** is different from\nthe original class, but their prototypes are the same. So, operations by `getPrototypeOf` method,\n`isPrototypeOf` method and the `instanceof` operator should work normally as usual.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass GrandParentClass\n{\n}\n\nclass ParentClass extends GrandParentClass\n{\n}\n\nclass NormalClass extends ParentClass\n{\n    __get(prop) {\n        return prop\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n/* getPrototypeOf */\n\nconsole.log(Object.getPrototypeOf(NormalClass) === ParentClass) // (boolean) true\nconsole.log(Object.getPrototypeOf(MagicClass) === ParentClass)  // (boolean) true\n\n/* isPrototypeOf */\n\nconsole.log(ParentClass.isPrototypeOf(NormalClass))             // (boolean) true\nconsole.log(ParentClass.isPrototypeOf(MagicClass))              // (boolean) true\nconsole.log(GrandParentClass.isPrototypeOf(NormalClass))        // (boolean) true\nconsole.log(GrandParentClass.isPrototypeOf(MagicClass))         // (boolean) true\n\n/* instanceof */\n\nconst normalInstance = new NormalClass()\nconst magicInstance = new MagicClass()\n/* or */\n// const magicInstance = magic(normalInstance)\n\nconsole.log(normalInstance instanceof MagicClass)               // (boolean) true\nconsole.log(normalInstance instanceof NormalClass)              // (boolean) true\nconsole.log(normalInstance instanceof ParentClass)              // (boolean) true\nconsole.log(normalInstance instanceof GrandParentClass)         // (boolean) true\n// `instanceof MagicClass = true` but no magic\nconsole.log(normalInstance.value)                               // (undefined) undefined\n\nconsole.log(magicInstance instanceof MagicClass)                // (boolean) true\nconsole.log(magicInstance instanceof NormalClass)               // (boolean) true\nconsole.log(magicInstance instanceof ParentClass)               // (boolean) true\nconsole.log(magicInstance instanceof GrandParentClass)          // (boolean) true\n// Magic!\nconsole.log(magicInstance.value)                                // (string) 'value'\n```\n\n***Note*: Operation by `setPrototypeOf` method is not allowed. Trying to apply it\nto magic classes or instances will throw `TypeError` exception.\n\n### Strict mode\n\nStrict mode is on by default while activating the magic. It will raise exceptions in following cases:\n\n- Writing data to non-existing properties while magic `__set` is not declared.\n- Reading data from non-existing properties while magic `__call` is not declared, and\n  magic `__get` is not declared or returns `undefined`.\n- Deleting existing properties.\n- Writing data to non-existing static properties while magic static `__set` is not declared.\n- Reading data from non-existing properties while magic static `__call` is not declared, and\n  magic static `__get` is not declared or returns `undefined`.\n- Deleting existing static properties.\n\nTo turn off strict mode, pass the `false` value as the second parameter while calling `magic` function.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static normal = 'normal'\n\n    normal = 'normal'\n}\n\n/* Strict mode is ON */\n\nconst StrictMagicClass = magic(NormalClass)\nconst strictMagicInstance = new StrictMagicClass()\n// get non-existing prop\ntry {\n    console.log(strictMagicInstance.magic)\n}\ncatch (e) {\n    console.log(e.message) // (string) 'Property [magic] does not exist.'\n}\n// set non-existing prop\ntry {\n    strictMagicInstance.magic = 'magic'\n}\ncatch (e) {\n    console.log(e.message) // (string) 'Property [magic] does not exist.'\n}\n// delete existing prop\ntry {\n    delete strictMagicInstance.normal\n}\ncatch (e) {\n    console.log(e.message) // (string) 'Cannot delete property [normal].'\n}\n// get non-existing static prop\ntry {\n    console.log(StrictMagicClass.magic)\n}\ncatch (e) {\n    console.log(e.message) // (string) 'Static property [magic] does not exist.'\n}\n// set non-existing static prop\ntry {\n    StrictMagicClass.magic = 'magic'\n}\ncatch (e) {\n    console.log(e.message) // (string) 'Static property [magic] does not exist.'\n}\n// delete existing static prop\ntry {\n    delete StrictMagicClass.normal\n}\ncatch (e) {\n    console.log(e.message) // (string) 'Cannot delete static property [normal].'\n}\n\n/* Strict mode is OFF */\n\nconst NotStrictMagicClass = magic(NormalClass, false)\nconst notStrictMagicInstance = new NotStrictMagicClass()\n// get non-existing prop\nconsole.log(notStrictMagicInstance.magic)          // (undefined) undefined\nconsole.log('magic' in notStrictMagicInstance)     // (boolean) false\n// set non-existing prop\nnotStrictMagicInstance.magic = 'magic'\nconsole.log(notStrictMagicInstance.magic)          // (string) 'magic'\nconsole.log('magic' in notStrictMagicInstance)     // (boolean) true\n// delete existing prop\ndelete notStrictMagicInstance.normal\nconsole.log(notStrictMagicInstance.normal)         // (undefined) 'undefined'\nconsole.log('normal' in notStrictMagicInstance)    // (boolean) false\n// get non-existing static prop\nconsole.log(NotStrictMagicClass.magic)             // (undefined) undefined\nconsole.log('magic' in NotStrictMagicClass)        // (boolean) false\n// set non-existing static prop\nNotStrictMagicClass.magic = 'magic'\nconsole.log(NotStrictMagicClass.magic)             // (string) 'magic'\nconsole.log('magic' in NotStrictMagicClass)        // (boolean) true\n// delete existing static prop\ndelete NotStrictMagicClass.normal\nconsole.log(NotStrictMagicClass.normal)            // (undefined) 'undefined'\nconsole.log('normal' in NotStrictMagicClass)       // (boolean) false\n```\n\n### Special magic static methods\n\n#### Static `__instance`\n\nThis magic static method is to create instance of the class without using `new` operator.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    constructor(...parameters) {\n        this.parameters = parameters\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n/* `new` operator */\nconst magicInstance1 = new MagicClass(1, 2, 3)\nconsole.log(magicInstance2.parameters) // (array) [1, 2, 3]\n/* magic `__instance` */\nconst magicInstance2 = MagicClass.__instance(1, 2, 3)\nconsole.log(magicInstance1.parameters) // (array) [1, 2, 3]\n```\n\n#### Static `__singleton`\n\nThis magic static method is to create only one instance of the class.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    constructor(...parameters) {\n        this.parameters = parameters\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n// create a singleton instance\nconst magicInstance1 = MagicClass.__singleton(1, 2, 3)\nconsole.log(magicInstance1.parameters)                      // (array) [1, 2, 3]\n\n// create new instance via `new` operator? \nconst magicInstance2 = new MagicClass(4, 5, 6)\nconst magicInstance3 = new MagicClass()\n// no, it's the same with the first instance\nconsole.log(magicInstance2 === magicInstance1)              // (boolean) true\nconsole.log(magicInstance2.parameters)                      // (array) [1, 2, 3]\nconsole.log(magicInstance3 === magicInstance1)              // (boolean) true\nconsole.log(magicInstance3.parameters)                      // (array) [1, 2, 3]\n\n// create new instance via magic `__instance`? \nconst magicInstance4 = MagicClass.__instance(7, 8, 9)\nconst magicInstance5 = MagicClass.__instance()\n// no, it's the same with the first instance\nconsole.log(magicInstance4 === magicInstance1)              // (boolean) true\nconsole.log(magicInstance4.parameters)                      // (array) [1, 2, 3]\nconsole.log(magicInstance5 === magicInstance1)              // (boolean) true\nconsole.log(magicInstance5.parameters)                      // (array) [1, 2, 3]\n\n// create new instance via magic `__singleton` again? \nconst magicInstance6 = MagicClass.__singleton(9, 10, 11)\nconst magicInstance7 = MagicClass.__singleton()\n// no, it's the same with the first instance\nconsole.log(magicInstance6 === magicInstance1)              // (boolean) true\nconsole.log(magicInstance6.parameters)                      // (array) [1, 2, 3]\nconsole.log(magicInstance7 === magicInstance1)              // (boolean) true\nconsole.log(magicInstance7.parameters)                      // (array) [1, 2, 3]\n```\n\n***Note:* The instances created before the first call to magic static `__singleton` are different from\nthe instance created by magic static `__singleton`.\n\n### Inheritance\n\nDeclaring a magic subclass inherits directly from the magic class is possible, but **not recommended**.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    magicProps = {}\n\n    __set(prop, value) {\n        this.magicProps[prop] = value\n    }\n\n    __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return prop\n    }\n\n    __call(method, ...parameters) {\n        return {method, parameters}\n    }\n\n    __invoke(...parameters) {\n        return {parameters}\n    }\n\n    __has(prop) {\n        return prop in this.magicProps\n    }\n\n    __delete(prop) {\n        return delete this.magicProps[prop]\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n/* magic subclass inherits directly from magic class */\nconst SubMagicClass = class extends MagicClass\n{\n    subMagicProps = {\n        subMagic: true,\n    }\n\n    /* overrides magic `__set` */\n    __set(prop, value) {\n        super.__set(prop, {sub: value})\n    }\n\n    /* overrides magic `__get` */\n    __get(prop) {\n        const value = super.__get(prop)\n        return value === undefined ? undefined : {sub: value}\n    }\n\n    /* overrides magic `__call` */\n    __call(method, ...parameters) {\n        return {sub: super.__call(method, ...parameters)}\n    }\n\n    /* overrides magic `__invoke` */\n    __invoke(...parameters) {\n        return {sub: super.__invoke(...parameters)}\n    }\n\n    /* overrides magic `__has` */\n    __has(prop) {\n        return super.__has(prop) || prop in this.subMagicProps\n    }\n\n    /* overrides magic `__delete` */\n    __delete(prop) {\n        super.__delete(prop)\n        return delete this.subMagicProps[prop]\n    }\n}\n\n// create sub magic instance\nconst subMagicInstance = new SubMagicClass\n// magic `__set` still works\nsubMagicInstance.magic = true\nconsole.log(subMagicInstance.magicProps)                // (object) {magic: {sub: true}}\n// magic `__get` still works\nconsole.log(subMagicInstance.magic)                     // (object) {sub: 'magic'})\n// magic `__call` still works\nconsole.log(subMagicInstance.callMagic('magic', true))  // (object) {sub: {method: 'callMagic', parameters: ['magic', true]}}\n// magic `__invoke` still works\nconsole.log(subMagicInstance('magic', true))            // (object) {sub: {parameters: ['magic', true]}}\n// magic `__has` still works\nconsole.log('subMagic' in subMagicInstance)             // (boolean) true\n// magic `__delete` still works\nconsole.log(delete subMagicInstance.subMagic)           // (boolean) true\nconsole.log('subMagic' in subMagicInstance)             // (boolean) false\nconsole.log(subMagicInstance.subMagicProps)             // (object) {}\n```\n\nThe reason is static properties/methods (including [magic static methods](#magic-static-methods)\nand [special ones](#special-magic-static-methods)) cannot be overridden with direct inheritance.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    static magicProps = {}\n\n    static __set(prop, value) {\n        this.magicProps[prop] = value\n    }\n\n    static __get(prop) {\n        if (prop.startsWith('call')) {\n            return undefined\n        }\n        return prop\n    }\n\n    static __call(method, ...parameters) {\n        return {method, parameters}\n    }\n\n    static __has(prop) {\n        return prop in this.magicProps\n    }\n\n    static __delete(prop) {\n        return delete this.magicProps[prop]\n    }\n}\n\nconst MagicClass = magic(NormalClass)\n\n/* magic subclass inherits directly from magic class */\nconst SubMagicClass = class extends MagicClass\n{\n    static subMagicProps = {\n        subMagic: true,\n    }\n\n    /* overrides magic static `__set` */\n    static __set(prop, value) {\n        super.__set(prop, {sub: value})\n    }\n\n    /* overrides magic static `__get` */\n    static __get(prop) {\n        const value = super.__get(prop)\n        return value === undefined ? undefined : {sub: value}\n    }\n\n    /* overrides magic static `__call` */\n    static __call(method, ...parameters) {\n        return {sub: super.__call(method, ...parameters)}\n    }\n\n    /* overrides magic static `__has` */\n    static __has(prop) {\n        return super.__has(prop) || prop in this.subMagicProps\n    }\n\n    /* overrides magic static `__delete` */\n    static __delete(prop) {\n        super.__delete(prop)\n        return delete this.subMagicProps[prop]\n    }\n}\n\n// magic static `__set` not work as expected\nSubMagicClass.magic = true\nconsole.log(SubMagicClass.magicProps)                   // (object) {magic: true} // expected: (object) {magic: {sub: true}}\n// magic static `__get` not work as expected\nconsole.log(SubMagicClass.magic)                        // (string) 'magic' // expected: (object) {sub: 'magic'})\n// magic static `__call` not work as expected\nconsole.log(SubMagicClass.callMagic('magic', true))     // (object) {method: 'callMagic', parameters: ['magic', true]} // expected: (object) {sub: {method: 'callMagic', parameters: ['magic', true]}}\n// magic static `__has` still works\nconsole.log('subMagic' in SubMagicClass)                // (boolean) false // expected: (boolean) true\n// magic static `__delete` still works\nconsole.log(delete SubMagicClass.subMagic)              // (boolean) true\nconsole.log('subMagic' in SubMagicClass)                // (boolean) false\nconsole.log(SubMagicClass.subMagicProps)                // (object) {subMagic: true} // expected: (object) {}\n```\n\nThe **recommended** way:\n\n- Firstly, declaring the subclass inherits from the original class.\n- Then, apply the magic to the subclass to get the magic subclass.\n\n```javascript\nconst magic = require('magic-class')\n/* or ES6 */\n// import magic from 'magic-class'\n\nclass NormalClass\n{\n    // ...\n}\n\nconst MagicClass = magic(NormalClass)\n\n// 1. Declaring the subclass inherits from the original class\nclass SubNormalClass extends NormalClass\n{\n    // ...\n}\n\n// 2. Apply the magic to the subclass to get the magic subclass\nconst SubMagicClass = magic(SubNormalClass)\n\n// ...\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinhntaim%2Fmagic-class","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flinhntaim%2Fmagic-class","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinhntaim%2Fmagic-class/lists"}