├── cjs ├── package.json └── index.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── package.json ├── esm └── index.js ├── test.js ├── README.md └── benchmark.js /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | benchmark.js 4 | test.js 5 | package-lock.json 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - master 9 | - /^greenkeeper/.*$/ 10 | after_success: 11 | - "npm run coveralls" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-record", 3 | "version": "1.0.1", 4 | "description": "A 25 LOC utility to define and create records via objects literals.", 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "scripts": { 8 | "build": "npm run cjs && npm run fix && npm test", 9 | "cjs": "ascjs esm cjs", 10 | "fix": "sed -i 's/(m => .* m)//g' cjs/index.js", 11 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 12 | "test": "nyc node test.js" 13 | }, 14 | "keywords": [ 15 | "record", 16 | "struct", 17 | "mixins", 18 | "basic", 19 | "simple" 20 | ], 21 | "author": "Andrea Giammarchi", 22 | "license": "ISC", 23 | "devDependencies": { 24 | "ascjs": "^3.1.2", 25 | "coveralls": "^3.0.9", 26 | "nyc": "^15.0.0" 27 | }, 28 | "dependencies": { 29 | "@ungap/get-own-property-descriptors": "^1.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | import getOwnPropertyDescriptors from '@ungap/get-own-property-descriptors'; 2 | const {assign, defineProperties} = Object; 3 | export function create(Record, init) { 4 | return arguments.length < 2 ? new Record : assign(new Record, init); 5 | }; 6 | export function define() { 7 | function Record() {} 8 | const records = [Record]; 9 | const {prototype} = Record; 10 | for (let i = 0, {length} = arguments; i < length; i++) { 11 | const curr = arguments[i]; 12 | if (typeof curr === 'function') { 13 | defineProperties(prototype, getOwnPropertyDescriptors(curr.prototype)); 14 | records.push(curr); 15 | } 16 | else 17 | defineProperties(prototype, getOwnPropertyDescriptors(curr)); 18 | } 19 | defineProperties(prototype, {constructor: { 20 | writable: false, 21 | value: Record 22 | }}); 23 | return defineProperties(Record, {implements: { 24 | value: Record => records.includes(Record) 25 | }});; 26 | }; 27 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const getOwnPropertyDescriptors = (require('@ungap/get-own-property-descriptors')); 3 | const {assign, defineProperties} = Object; 4 | function create(Record, init) { 5 | return arguments.length < 2 ? new Record : assign(new Record, init); 6 | } 7 | exports.create = create; 8 | function define() { 9 | function Record() {} 10 | const records = [Record]; 11 | const {prototype} = Record; 12 | for (let i = 0, {length} = arguments; i < length; i++) { 13 | const curr = arguments[i]; 14 | if (typeof curr === 'function') { 15 | defineProperties(prototype, getOwnPropertyDescriptors(curr.prototype)); 16 | records.push(curr); 17 | } 18 | else 19 | defineProperties(prototype, getOwnPropertyDescriptors(curr)); 20 | } 21 | defineProperties(prototype, {constructor: { 22 | writable: false, 23 | value: Record 24 | }}); 25 | return defineProperties(Record, {implements: { 26 | value: Record => records.includes(Record) 27 | }});; 28 | } 29 | exports.define = define; 30 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const {create, define} = require('./cjs'); 2 | 3 | // define a Record class through one or more arguments 4 | const Point2D = define({x: 0, y: 0}); 5 | 6 | // define mixins by passing records and/or literals 7 | const Point3D = define(Point2D, {z: 0}); 8 | 9 | // create an instance via new Record or create(Record) 10 | const p2d = create(Point2D); 11 | console.assert( 12 | p2d.x === 0 && 13 | p2d.y === 0 && 14 | Point2D.implements(Point2D) === true && 15 | Point2D.implements(Point3D) === false // Point2D is not Point3D 16 | ); 17 | 18 | // optionally pass an `init` object to assign its values 19 | const p3d = create(Point3D, {y: 123, z: 9}); 20 | const {constructor} = p3d; 21 | console.assert( 22 | p3d.x === 0 && 23 | p3d.y === 123 && 24 | p3d.z === 9 && 25 | constructor.implements(Point3D) === true && 26 | constructor.implements(Point2D) === true // Point3D is also Point2D 27 | ); 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # basic-record 2 | 3 | **Social Media Photo by [Adrian Korte](https://unsplash.com/@adkorte) on [Unsplash](https://unsplash.com/)** 4 | 5 | [![Build Status](https://travis-ci.com/WebReflection/basic-record.svg?branch=master)](https://travis-ci.com/WebReflection/basic-record) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/basic-record/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/basic-record?branch=master) 6 | 7 | A 25 LOC utility to define and create records via objects literals. 8 | 9 | Each `Record` class will have an `implements(Record)` method that returns `true` if the passed `Record` was used during class definition. 10 | 11 | ```js 12 | const {create, define} = require('basic-record'); 13 | 14 | // define a Record class through one or more arguments 15 | const Point2D = define({x: 0, y: 0}); 16 | 17 | // define mixins by passing records and/or literals 18 | const Point3D = define(Point2D, {z: 0}); 19 | 20 | // create an instance via new Record or create(Record) 21 | const p2d = create(Point2D); 22 | console.assert( 23 | p2d.x === 0 && 24 | p2d.y === 0 && 25 | Point2D.implements(Point2D) === true && 26 | Point2D.implements(Point3D) === false // Point2D is not Point3D 27 | ); 28 | 29 | // optionally pass an `init` object to assign its values 30 | const p3d = create(Point3D, {y: 123, z: 9}); 31 | const {constructor} = p3d; 32 | console.assert( 33 | p3d.x === 0 && 34 | p3d.y === 123 && 35 | p3d.z === 9 && 36 | constructor.implements(Point3D) === true && 37 | constructor.implements(Point2D) === true // Point3D is also Point2D 38 | ); 39 | ``` 40 | 41 | 42 | ### Use cases & Benchmark 43 | 44 | The peculiarity of records is their ability to define every default value directly through their prototype. 45 | The benefits are seen particularly in creation of many default objects, but also assignment of partial properties, as opposite of guarding each received property, or argument, with a default value. 46 | 47 | The [benchmark](./benchmark.js) file reflects these goals, underlying where it's easier, and faster, to use records instead of regular classes instances. 48 | 49 | 50 | #### Results: 51 | 52 | ``` 53 | class.create: 0.299ms 54 | record.create: 0.023ms 55 | 56 | class.createArgs: 0.016ms 57 | record.createArgs: 0.073ms 58 | 59 | class.createExtend: 0.443ms 60 | record.createExtend: 0.02ms 61 | 62 | class.createExtendArgs: 0.014ms 63 | record.createExtendArgs: 0.061ms 64 | 65 | class.createPartialDefaults: 0.056ms 66 | record.createPartialDefaults: 0.044ms 67 | 68 | class.copy: 0.256ms 69 | record.copy: 0.058ms 70 | 71 | class.copyExtend: 0.528ms 72 | record.copyExtend: 0.063ms 73 | 74 | class.getCoords: 0.455ms 75 | record.getCoords: 0.009ms 76 | 77 | class.getCoordsArgs: 0.018ms 78 | record.getCoordsArgs: 0.007ms 79 | ``` 80 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | const {create, define} = require('./cjs'); 2 | 3 | class CPoint2D { 4 | x = 0; 5 | y = 0; 6 | get coords() { 7 | return [this.x, this.y]; 8 | } 9 | } 10 | 11 | class CPoint2DArgs { 12 | constructor({x, y}) { 13 | this.x = x; 14 | this.y = y; 15 | } 16 | get coords() { 17 | return [this.x, this.y]; 18 | } 19 | } 20 | 21 | class CPoint3D extends CPoint2D { 22 | x = 0; 23 | y = 0; 24 | z = 0; 25 | get coords() { 26 | return [this.x, this.y, this.z]; 27 | } 28 | } 29 | 30 | class CPoint3DArgs extends CPoint2DArgs { 31 | constructor({x, y, z}) { 32 | super({x, y}); 33 | this.z = z; 34 | } 35 | get coords() { 36 | return [this.x, this.y, this.z]; 37 | } 38 | } 39 | 40 | const RPoint2D = define({ 41 | x: 0, 42 | y: 0, 43 | get cords() { 44 | return [this.x, this.y]; 45 | } 46 | }); 47 | 48 | const RPoint3D = define(RPoint2D, { 49 | z: 0, 50 | get cords() { 51 | return [this.x, this.y, this.z]; 52 | } 53 | }); 54 | 55 | const benchmark = { 56 | class: { 57 | create() { 58 | const out = []; 59 | for (let i = 0; i < 1000; i++) 60 | out.push(new CPoint2D); 61 | return out; 62 | }, 63 | createArgs() { 64 | const out = []; 65 | for (let i = 0; i < 1000; i++) 66 | out.push(new CPoint2DArgs({x: 1, y: 2})); 67 | return out; 68 | }, 69 | createExtend() { 70 | const out = []; 71 | for (let i = 0; i < 1000; i++) 72 | out.push(new CPoint3D); 73 | return out; 74 | }, 75 | createExtendArgs() { 76 | const out = []; 77 | for (let i = 0; i < 1000; i++) 78 | out.push(new CPoint3DArgs({x: 1, y: 2, z: 3})); 79 | return out; 80 | }, 81 | createPartialDefaults() { 82 | const out = []; 83 | const defaults = {x: 0, y: 0, z: 0}; 84 | for (let i = 0; i < 1000; i++) 85 | out.push(new CPoint3DArgs({...defaults, x: i})); 86 | return out; 87 | }, 88 | copy() { 89 | const out = []; 90 | const original = new CPoint2D; 91 | original.x = 1; 92 | original.y = 2; 93 | for (let i = 0; i < 1000; i++) 94 | out.push(Object.assign(new CPoint2D, original)); 95 | return out; 96 | }, 97 | copyExtend() { 98 | const out = []; 99 | const original = new CPoint3D; 100 | original.x = 1; 101 | original.y = 2; 102 | original.z = 3; 103 | for (let i = 0; i < 1000; i++) 104 | out.push(Object.assign(new CPoint3D, original)); 105 | return out; 106 | }, 107 | getCoords() { 108 | const out = []; 109 | for (let i = 0; i < 1000; i++) 110 | out.push((new CPoint3D).coords); 111 | return out; 112 | }, 113 | getCoordsArgs() { 114 | const out = []; 115 | for (let i = 0; i < 1000; i++) 116 | out.push((new CPoint3DArgs({x: 0, y: 0, z: 0})).coords); 117 | return out; 118 | } 119 | }, 120 | record: { 121 | create() { 122 | const out = []; 123 | for (let i = 0; i < 1000; i++) 124 | out.push(new RPoint2D); 125 | return out; 126 | }, 127 | createArgs() { 128 | const out = []; 129 | for (let i = 0; i < 1000; i++) 130 | out.push(create(RPoint2D, {x: 1, y: 2})); 131 | return out; 132 | }, 133 | createExtend() { 134 | const out = []; 135 | for (let i = 0; i < 1000; i++) 136 | out.push(new RPoint3D); 137 | return out; 138 | }, 139 | createExtendArgs() { 140 | const out = []; 141 | for (let i = 0; i < 1000; i++) 142 | out.push(create(RPoint3D, {x: 1, y: 2, z: 3})); 143 | return out; 144 | }, 145 | createPartialDefaults() { 146 | const out = []; 147 | for (let i = 0; i < 1000; i++) 148 | out.push(create(RPoint3D, {x: i})); 149 | return out; 150 | }, 151 | copy() { 152 | const out = []; 153 | const original = create(RPoint2D, {x: 1, y: 2}); 154 | for (let i = 0; i < 1000; i++) 155 | out.push(create(RPoint2D, original)); 156 | return out; 157 | }, 158 | copyExtend() { 159 | const out = []; 160 | const original = create(RPoint3D, {x: 1, y: 2, z: 3}); 161 | for (let i = 0; i < 1000; i++) 162 | out.push(create(RPoint3D, original)); 163 | return out; 164 | }, 165 | getCoords() { 166 | const out = []; 167 | for (let i = 0; i < 1000; i++) 168 | out.push((new RPoint3D).coords); 169 | return out; 170 | }, 171 | getCoordsArgs() { 172 | const out = []; 173 | for (let i = 0; i < 1000; i++) 174 | out.push((new RPoint3D).coords); 175 | return out; 176 | } 177 | } 178 | }; 179 | 180 | const bench = name => { 181 | let i = 0, result = []; 182 | const method = name.split('.').reduce((o, k) => o[k], benchmark); 183 | // warm up invokes 184 | while (i++ < 100) 185 | method(); 186 | // benchmark 187 | console.time(name); 188 | result = method(); 189 | console.timeEnd(name); 190 | return result; 191 | }; 192 | 193 | setTimeout(() => { 194 | let result = null; 195 | console.log(''); 196 | result = bench('class.create'); 197 | result = bench('record.create'); 198 | console.log(''); 199 | result = bench('class.createArgs'); 200 | result = bench('record.createArgs'); 201 | console.log(''); 202 | result = bench('class.createExtend'); 203 | result = bench('record.createExtend'); 204 | console.log(''); 205 | result = bench('class.createExtendArgs'); 206 | result = bench('record.createExtendArgs'); 207 | console.log(''); 208 | result = bench('class.createPartialDefaults'); 209 | result = bench('record.createPartialDefaults'); 210 | console.log(''); 211 | result = bench('class.copy'); 212 | result = bench('record.copy'); 213 | console.log(''); 214 | result = bench('class.copyExtend'); 215 | result = bench('record.copyExtend'); 216 | console.log(''); 217 | result = bench('class.getCoords'); 218 | result = bench('record.getCoords'); 219 | console.log(''); 220 | result = bench('class.getCoordsArgs'); 221 | result = bench('record.getCoordsArgs'); 222 | console.log(''); 223 | return result; 224 | }); 225 | --------------------------------------------------------------------------------