├── .editorconfig ├── .eslintignore ├── .eslintignore-js ├── .eslintrc-js.json ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ └── privatize-plus-collision.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── arg-over-prop │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── check-compose │ ├── .eslintrc.json │ ├── README.md │ ├── assignment-tests.js │ ├── bin │ │ └── check-compose.js │ ├── compose-basic-tests.js │ ├── compose-tests.js │ ├── composers-tests.js │ ├── descriptor-tests.js │ ├── getters-setters-tests.js │ ├── index.ts │ ├── initializer-tests.js │ ├── instance-replacement-tests.js │ ├── merge-tests.js │ ├── package-lock.json │ ├── package.json │ ├── priority-tests.js │ ├── property-descriptor-tests.js │ ├── property-safety-tests.js │ ├── stamp-tests.js │ └── static-properties-tests.js ├── collision │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── compose │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.ts │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── configure │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── configure.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── convert-class │ ├── .jshintrc │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── core │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── merge.js │ ├── assign.js │ ├── assign.ts │ ├── get-own-property-keys.js │ ├── get-own-property-keys.ts │ ├── index.js │ ├── index.ts │ ├── merge.js │ ├── merge.ts │ ├── package.json │ └── tsconfig.json ├── eventemittable │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── eventemittable.js │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── fp-constructor │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── init-property │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── instanceof │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── instanceof.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── is │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ ├── composable.js │ │ ├── descriptor.js │ │ ├── exports.js │ │ ├── plain-object.js │ │ ├── stamp.js │ │ └── string.js │ ├── array.js │ ├── array.ts │ ├── composable.js │ ├── composable.ts │ ├── descriptor.js │ ├── descriptor.ts │ ├── function.js │ ├── function.ts │ ├── index.js │ ├── index.ts │ ├── object.js │ ├── object.ts │ ├── package.json │ ├── plain-object.js │ ├── plain-object.ts │ ├── stamp.js │ ├── stamp.ts │ ├── string.js │ ├── string.ts │ └── tsconfig.json ├── it │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ ├── index.js │ │ └── shortcut.js │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── named │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── privatize │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── required │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── shortcut │ ├── .npmignore │ ├── README.md │ ├── __tests__ │ └── index.js │ ├── index.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | packages/check-compose 3 | *.d.ts 4 | -------------------------------------------------------------------------------- /.eslintignore-js: -------------------------------------------------------------------------------- 1 | node_modules 2 | packages/* 3 | !packages/check-compose 4 | packages/check-compose/index.js 5 | packages/*/__test__/* 6 | -------------------------------------------------------------------------------- /.eslintrc-js.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | // "jest": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "plugin:es/no-2017", // NodeJS 8 support 8 | "eslint:recommended", 9 | "airbnb-base", 10 | "plugin:import/errors", 11 | "plugin:import/warnings", 12 | // "plugin:jest/all", 13 | "plugin:node/recommended", 14 | "plugin:prettier/recommended" // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 15 | ], 16 | "parserOptions": { 17 | "ecmaVersion": 2016 // NodeJS 8 support 18 | }, 19 | "plugins": [ 20 | "es", // should be unnecessary with drop of Node 8 or switch to Typescript 21 | "import", 22 | // "jest", // relevancy to ensure correct unit tests to be evaluated 23 | "node", 24 | "prettier" 25 | ], 26 | "reportUnusedDisableDirectives": true, 27 | "rules": { 28 | "class-methods-use-this": 1, 29 | "func-names": "off", 30 | "global-require": 1, 31 | "guard-for-in": 1, 32 | "max-classes-per-file": "off", 33 | "no-array-constructor": 1, 34 | "no-continue": 1, 35 | "no-nested-ternary": 1, 36 | "no-param-reassign": 1, 37 | "no-plusplus": 1, 38 | "no-process-exit": 1, 39 | "no-proto": 1, 40 | "no-restricted-syntax": [ 41 | "error", 42 | { 43 | "selector": "ForInStatement", 44 | "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." 45 | }, 46 | { 47 | "selector": "LabeledStatement", 48 | "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." 49 | }, 50 | { 51 | "selector": "WithStatement", 52 | "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." 53 | } 54 | ], 55 | "no-return-assign": 1, 56 | "no-underscore-dangle": 1, 57 | "no-unreachable": 1, 58 | "no-unused-vars": 1, 59 | "no-use-before-define": 1, 60 | "prefer-destructuring": 1, 61 | "prefer-object-spread": 0, 62 | "prefer-rest-params": 1, 63 | "prefer-spread": 1, 64 | "strict": 0, 65 | "valid-typeof": 1, 66 | "vars-on-top": 1, 67 | 68 | // eslint-plugin-es - https://mysticatea.github.io/eslint-plugin-es/ 69 | 70 | // eslint-plugin-import rules - https://github.com/benmosher/eslint-plugin-import/blob/master/README.md 71 | "import/no-dynamic-require": 1, 72 | 73 | // eslint-plugin-jest - https://github.com/jest-community/eslint-plugin-jest/blob/master/README.md 74 | // "jest/expect-expect": 1, 75 | // "jest/no-test-return-statement": 1, 76 | // "jest/no-truthy-falsy": 1, 77 | // "jest/prefer-called-with": 1, 78 | // "jest/prefer-expect-assertions": 0, 79 | // "jest/require-to-throw-message": 1, 80 | // "jest/require-top-level-describe": 1, 81 | // "jest/no-export": 1, 82 | 83 | // for reference. Might be useful with switch to Typescript 84 | // eslint-plugin-jsdoc - https://github.com/gajus/eslint-plugin-jsdoc/blob/master/README.md 85 | 86 | // eslint-plugin-node - https://github.com/mysticatea/eslint-plugin-node 87 | "node/no-unpublished-require": 1, 88 | "node/no-deprecated-api": 1, 89 | 90 | "prettier/prettier": "error" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "jest": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "airbnb-base", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:import/errors", 12 | "plugin:import/warnings", 13 | "plugin:jest/all", 14 | "plugin:node/recommended", 15 | "plugin:prettier/recommended" // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 16 | ], 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaVersion": 2018, 20 | "sourceType": "module" 21 | }, 22 | "plugins": [ 23 | "@typescript-eslint", 24 | "import", 25 | "jest", // relevancy to ensure correct unit tests to be evaluated 26 | "node", 27 | "prefer-arrow", 28 | "prettier" 29 | ], 30 | "settings": { 31 | "import/parsers": { 32 | "@typescript-eslint/parser": [".ts", ".tsx"] 33 | }, 34 | "import/resolver": { 35 | // use /tsconfig.json 36 | "typescript": { 37 | "alwaysTryTypes": true // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` 38 | } 39 | }, 40 | "node": { 41 | "tryExtensions": [".ts", ".js", ".json"] 42 | } 43 | }, 44 | "reportUnusedDisableDirectives": true, 45 | "rules": { 46 | "@typescript-eslint/interface-name-prefix": 0, 47 | "@typescript-eslint/no-use-before-define": 1, 48 | 49 | "class-methods-use-this": 1, 50 | // "func-names": 0, 51 | "global-require": 1, 52 | "guard-for-in": 1, 53 | "max-classes-per-file": 0, 54 | "no-array-constructor": 1, 55 | "no-continue": 1, 56 | "no-nested-ternary": 1, 57 | "no-param-reassign": 1, 58 | "no-plusplus": 1, 59 | "no-process-exit": 1, 60 | "no-proto": 1, 61 | "no-restricted-syntax": [ 62 | "error", 63 | { 64 | "selector": "ForInStatement", 65 | "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." 66 | }, 67 | { 68 | "selector": "LabeledStatement", 69 | "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." 70 | }, 71 | { 72 | "selector": "WithStatement", 73 | "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." 74 | } 75 | ], 76 | "no-return-assign": 1, 77 | "no-underscore-dangle": 1, 78 | "no-unreachable": 1, 79 | "no-unused-vars": 0, 80 | "no-use-before-define": 0, 81 | "prefer-destructuring": 1, 82 | // "prefer-object-spread": 0, 83 | "prefer-rest-params": 1, 84 | "prefer-spread": 1, 85 | "strict": 0, 86 | "valid-typeof": 1, 87 | "vars-on-top": 1, 88 | 89 | // eslint-plugin-es - https://mysticatea.github.io/eslint-plugin-es/ 90 | 91 | // eslint-plugin-import rules - https://github.com/benmosher/eslint-plugin-import/blob/master/README.md 92 | "import/extensions": ["error", { "ts": "never" }], 93 | "import/no-dynamic-require": 1, 94 | "import/no-extraneous-dependencies": 0, 95 | "import/prefer-default-export": 0, 96 | 97 | // eslint-plugin-jest - https://github.com/jest-community/eslint-plugin-jest/blob/master/README.md 98 | "jest/expect-expect": 1, 99 | "jest/no-test-return-statement": 1, 100 | "jest/no-truthy-falsy": 1, 101 | "jest/prefer-called-with": 1, 102 | "jest/prefer-expect-assertions": 0, 103 | "jest/require-to-throw-message": 1, 104 | "jest/require-top-level-describe": 1, 105 | "jest/no-export": 1, 106 | 107 | // for reference. Might be useful with switch to Typescript 108 | // eslint-plugin-jsdoc - https://github.com/gajus/eslint-plugin-jsdoc/blob/master/README.md 109 | 110 | // eslint-plugin-node - https://github.com/mysticatea/eslint-plugin-node 111 | "node/no-extraneous-import": ["error", { "allowModules": ["ts-essentials"] }], 112 | "node/no-unpublished-require": 1, 113 | "node/no-unsupported-features/es-syntax": 0, 114 | "node/no-deprecated-api": 1, 115 | "prettier/prettier": "error" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ##### SauceLabs 2 | buildconfig.env 3 | server.js 4 | sauce_connect.log 5 | /dist/ 6 | 7 | 8 | ###### Mac OS generated files 9 | .DS_Store 10 | .DS_Store? 11 | ._* 12 | .Spotlight-V100 13 | .Trashes 14 | ehthumbs.db 15 | Thumbs.db 16 | 17 | 18 | 19 | ##### node.js 20 | # Logs 21 | logs 22 | *.log 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (http://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directory 45 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 46 | node_modules 47 | .npmrc 48 | # package-lock.json 49 | 50 | 51 | 52 | ##### Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 53 | *.iml 54 | 55 | ## Directory-based project format: 56 | .idea/ 57 | # if you remove the above rule, at least ignore the following: 58 | 59 | # User-specific stuff: 60 | # .idea/workspace.xml 61 | # .idea/tasks.xml 62 | # .idea/dictionaries 63 | 64 | # Sensitive or high-churn files: 65 | # .idea/dataSources.ids 66 | # .idea/dataSources.xml 67 | # .idea/sqlDataSources.xml 68 | # .idea/dynamic.xml 69 | # .idea/uiDesigner.xml 70 | 71 | # Gradle: 72 | # .idea/gradle.xml 73 | # .idea/libraries 74 | 75 | # Mongo Explorer plugin: 76 | # .idea/mongoSettings.xml 77 | 78 | ## File-based project format: 79 | *.ipr 80 | *.iws 81 | 82 | ## Plugin-specific files: 83 | 84 | # IntelliJ 85 | /out/ 86 | 87 | # mpeltonen/sbt-idea plugin 88 | .idea_modules/ 89 | 90 | # JIRA plugin 91 | atlassian-ide-plugin.xml 92 | 93 | # Crashlytics plugin (for Android Studio and IntelliJ) 94 | com_crashlytics_export_strings.xml 95 | crashlytics.properties 96 | crashlytics-build.properties 97 | 98 | 99 | 100 | ##### VIM editor 101 | [._]*.s[a-w][a-z] 102 | [_]s[a-w][a-z] 103 | *.un~ 104 | Session.vim 105 | .netrwhist 106 | *~ 107 | 108 | # Typescript 109 | *.tsbuildinfo 110 | 111 | ##### VS Code 112 | *.code-workspace 113 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "printWidth": 120, 6 | "quoteProps": "as-needed", 7 | "semi": true, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "es5", 11 | "useTabs": false 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | - '12' 5 | before_script: 6 | - npm run build-clean 7 | script: 8 | - npm run ci 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 stampit-org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stamp 2 | 3 | _Mono repository for stamp related packages._ 4 | 5 | Documentation: https://stampit.js.org 6 | 7 | * `@stamp/arg-over-prop` - Assign properties passed to the stamp factory 8 | * `@stamp/check-compose` - Command line tool to test your 'compose' function implementation 9 | * `@stamp/collision` - Detect and manipulate method collisions 10 | * `@stamp/compose` - Compose function implementation to create stamps 11 | * `@stamp/configure` - Access configuration of your stamps anywhere 12 | * `@stamp/core` - Core functions for creating stamps 13 | * `@stamp/eventemittable` - Node.js' EventEmitter as a stamp 14 | * `@stamp/fp-constructor` - Adds the Functional Programming capabilities to your stamps 15 | * `@stamp/init-property` - Replaces properties which reference stamps with objects created from the stamps 16 | * `@stamp/instanceof` - Enables `obj instanceof MyStamp` in ES6 environments 17 | * `@stamp/is` - Various checking functions used with stamps 18 | * `@stamp/it` - A nice, handy API implementation of the compose standard 19 | * `@stamp/named` - (Re)Name a stamp factory function 20 | * `@stamp/privatize` - Protect private properties 21 | * `@stamp/required` - Insist on a method/property/staticProperty/configuration presence 22 | * `@stamp/shortcut` - Adds handy shortcuts for stamp composition 23 | 24 | # Changelog 25 | 26 | * **v0.x.x** were development versions. 27 | * **v1.x.x** are production ready versions. No API changes comparing to v0.x.x. 28 | 29 | # Development 30 | 31 | ## Install 32 | 33 | Run `npm install` as usual followed by `npm run bootstrap` which creates local references to modules that require each other. 34 | 35 | 1. `npx lerna init` 36 | 2. `npm install` 37 | 3. `npx lerna run build` 38 | 39 | ## Tests 40 | 41 | The `npm test` command will execute [Jest](https://github.com/facebook/jest) tests, and then [JSHint](https://github.com/jshint/jshint). 42 | 43 | The `npm test -- --watch` command will watch file changes and execute tests as needed. 44 | -------------------------------------------------------------------------------- /__tests__/privatize-plus-collision.js: -------------------------------------------------------------------------------- 1 | var compose = require('../packages/compose'); 2 | var Collision = require('../packages/collision'); 3 | var Privatize = require('../packages/privatize'); 4 | 5 | describe('Collision + Privatize', function () { 6 | it('work together', function () { 7 | var Stamp = compose( 8 | Collision, 9 | Privatize, 10 | { 11 | methods: { 12 | privateMethod: function () {} 13 | } 14 | } 15 | ) 16 | .collisionProtectAnyMethod() 17 | .privatizeMethods('privateMethod'); 18 | 19 | var obj = Stamp(); 20 | 21 | expect(obj.privateMethod).toBeUndefined(); 22 | }); 23 | 24 | it('privatizes deferred methods', function () { 25 | // General purpose behavior to defer "draw()" method collisions 26 | var DeferDraw = Collision.collisionSetup({defer: ['draw']}); 27 | 28 | var Border = compose(DeferDraw, { 29 | methods: { 30 | draw: jest.fn() // Spy function 31 | } 32 | }); 33 | var Button = compose(DeferDraw, { 34 | methods: { 35 | draw: jest.fn() // Spy function 36 | } 37 | }); 38 | 39 | // General purpose behavior to privatize the "draw()" method 40 | var PrivateDraw = Privatize.privatizeMethods('draw'); 41 | 42 | // General purpose behavior to forbid the "redraw()" method collision 43 | var ForbidRedrawCollision = Collision.collisionSetup({forbid: ['redraw']}); 44 | 45 | // The aggregating component 46 | var ModalDialog = compose(PrivateDraw) // privatize the "draw()" method 47 | .compose(Border, Button) // modal will consist of Border and Button 48 | .compose(ForbidRedrawCollision) // there can be only one "redraw()" method 49 | .compose({ 50 | methods: { 51 | // the public method which calls the deferred private methods 52 | redraw: function (int) { 53 | this.draw(int); 54 | } 55 | } 56 | }); 57 | 58 | // Creating an instance of the stamp 59 | var dialog = ModalDialog(); 60 | 61 | // Check if the "draw()" method is actually privatized 62 | expect(dialog.draw).toBeUndefined(); 63 | 64 | // Calling the public method, which calls the deferred private methods 65 | dialog.redraw(42); 66 | 67 | // Check if the spy "draw()" deferred functions were actually invoked 68 | expect(Border.compose.methods.draw).toBeCalledWith(42); 69 | expect(Button.compose.methods.draw).toBeCalledWith(42); 70 | 71 | // Make sure the ModalDialog throws every time on the "redraw()" collisions 72 | var HaveRedraw = compose({methods: {redraw: jest.fn()}}); 73 | expect(function () {compose(ModalDialog, HaveRedraw)}).toThrow(); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "useWorkspaces": true, 4 | "version": "0.0.0" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stamp", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "description": "The stamp ecosystem", 6 | "author": "", 7 | "contributors": [ 8 | "Vasyl Boroviak (https://twitter.com/kore_sar)", 9 | "Tim Routowicz (https://twitter.com/troutowicz)", 10 | "Carl Olsen (https://twitter.com/unstoppableCarl)", 11 | "Daniel K. (https://twitter.com/FredyCrueger)", 12 | "Christopher Hiller (https://twitter.com/b0neskull)", 13 | "PopGoesTheWza (https://github.com/PopGoesTheWza)" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:stampit-org/stamp.git" 18 | }, 19 | "scripts": { 20 | "build": "lerna run build", 21 | "build-clean": "del-cli **/*.tsbuildinfo & npm run build", 22 | "ci": "npm run test", 23 | "test": "jest", 24 | "posttest": "npm run lint", 25 | "postinstall": "npm run build-clean", 26 | "publish": "lerna publish -i -- --access=public", 27 | "updated": "lerna updated", 28 | "clean": "lerna clean", 29 | "lint": "npm run lint-ts & npm run lint-js", 30 | "lint-js": "eslint --no-eslintrc --config .eslintrc-js.json --ext .js --ignore-path .eslintignore-js packages", 31 | "lint-js-fix": "eslint --config .eslintrc-js.json --ext .js --fix --ignore-path .eslintignore-js packages", 32 | "lint-ts": "eslint --ext .ts --ignore-path .eslintignore packages", 33 | "lint-ts-fix": "eslint --config .eslintrc.json --ext .ts --fix --ignore-path .eslintignore packages", 34 | "lint-fix": "npm run lint-ts-fix & npm run lint-js-fix" 35 | }, 36 | "engines": { 37 | "node": ">= 10.18.0" 38 | }, 39 | "devDependencies": { 40 | "@types/jest": "^29.2.5", 41 | "@typescript-eslint/eslint-plugin": "^5.48.0", 42 | "@typescript-eslint/parser": "^5.48.0", 43 | "del-cli": "^3.0.0", 44 | "eslint": "^8.31.0", 45 | "eslint-config-airbnb-base": "^15.0.0", 46 | "eslint-config-prettier": "^8.6.0", 47 | "eslint-import-resolver-typescript": "^3.5.2", 48 | "eslint-plugin-import": "^2.26.0", 49 | "eslint-plugin-jest": "^27.2.1", 50 | "eslint-plugin-node": "^11.1.0", 51 | "eslint-plugin-prefer-arrow": "^1.2.3", 52 | "eslint-plugin-prettier": "^4.2.1", 53 | "jest": "^29.3.1", 54 | "lerna": "^6.4.0", 55 | "prettier": "^2.8.2", 56 | "ts-jest": "^29.0.5", 57 | "typescript": "^4.9.4" 58 | }, 59 | "jest": { 60 | "preset": "ts-jest", 61 | "testEnvironment": "node", 62 | "coverageReporters": [ 63 | "html", 64 | "text" 65 | ] 66 | }, 67 | "dependencies": { 68 | "@stamp/compose": "^1.0.2" 69 | }, 70 | "workspaces": [ 71 | "./packages/*" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /packages/arg-over-prop/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/arg-over-prop/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/arg-over-prop 2 | 3 | _Assign properties passed to the stamp factory_ 4 | 5 | ## Usage 6 | 7 | ```js 8 | import ArgOverProp from '@stamp/arg-over-prop'; 9 | 10 | const StampA = compose({ 11 | properties: { 12 | foo: 1 13 | } 14 | }) 15 | .compose(ArgOverProp).argOverProp('foo'); 16 | 17 | const instance1 = StampA(); // { foo: 1 } 18 | const instance2 = StampA({ foo: 999 }); // { foo: 999 } 19 | ``` 20 | 21 | A shorter but identical version of the same `StampA`: 22 | ```js 23 | const StampA = ArgOverProp.argOverProp({foo: 1}); 24 | ``` 25 | 26 | Or if you don't want to import the stamp you can import only the method: 27 | ```js 28 | import {argOverProp} from '@stamp/arg-over-prop'; 29 | const StampA = argOverProp({foo: 1}); 30 | ``` 31 | 32 | 33 | Basically, the `arg-over-prop` stamp sets properties in an initializer. 34 | The code below is what the `StampA` becomes. 35 | ```js 36 | const StampA = compose({ 37 | properties: { 38 | foo: 1 39 | }, 40 | initializers: [function (opts) { 41 | this.foo = opts.foo; 42 | }] 43 | }) 44 | ``` 45 | 46 | 47 | ## Example 48 | 49 | ```js 50 | import ArgOverProp from '@stamp/arg-over-prop'; 51 | 52 | const HasColor = ArgOverProp.argOverProp('color', 'background'); 53 | const HasPoint = ArgOverProp.argOverProp({ x: 0, y: 0 }); 54 | 55 | const ColorPoint = compose(HasColor, HasPoint); 56 | 57 | const point = ColorPoint({ color: 'blue', x: 15 }); 58 | 59 | console.log(point.color); // "blue" 60 | console.log(point.background); // undefined 61 | console.log(point.x); // 15 62 | console.log(point.y); // 0 63 | ``` 64 | -------------------------------------------------------------------------------- /packages/arg-over-prop/__tests__/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ArgOverProp = require('..'); 4 | 5 | describe('@stamp/arg-over-prop', function() { 6 | it('can be used as a standalone function', function() { 7 | const { argOverProp } = ArgOverProp; 8 | const Stamp = argOverProp('override'); 9 | const instance = Stamp({ override: 1, dontTouch: 2 }); 10 | 11 | expect(instance.override).toBe(1); 12 | expect(instance).not.toHaveProperty('dontTouch'); 13 | }); 14 | 15 | it('assigns only the requested properties', function() { 16 | const Stamp = ArgOverProp.argOverProp('override'); 17 | const instance = Stamp({ override: 1, dontTouch: 2 }); 18 | 19 | expect(instance.override).toBe(1); 20 | expect(instance).not.toHaveProperty('dontTouch'); 21 | }); 22 | 23 | it('sets default property values if object was passed', function() { 24 | const Stamp = ArgOverProp.argOverProp({ override: 0, dontTouch: 0 }); 25 | const instance = Stamp({ override: 1 }); 26 | 27 | expect(instance.override).toBe(1); 28 | expect(instance.dontTouch).toBe(0); 29 | }); 30 | 31 | it('allow array arguments', function() { 32 | const Stamp = ArgOverProp.argOverProp(['override', 'dontTouch']); 33 | const instance = Stamp({ override: 1 }); 34 | 35 | expect(instance.override).toBe(1); 36 | expect(instance).not.toHaveProperty('dontTouch'); 37 | }); 38 | 39 | it('skips invalid arguments', function() { 40 | const Stamp = ArgOverProp.argOverProp(1, {}, [], NaN, function() {}, null, undefined); 41 | const instance = Stamp({ '1': 1 }); 42 | 43 | expect(instance).not.toHaveProperty('1'); 44 | }); 45 | 46 | it('does not crash in any circumstance', function() { 47 | const Stamp1 = ArgOverProp.argOverProp('x'); 48 | const Stamp2 = ArgOverProp.argOverProp({ x: 1 }); 49 | const Stamp3 = ArgOverProp.argOverProp(['x']); 50 | const S1 = Stamp1.compose(Stamp2, Stamp3); 51 | const S2 = Stamp1.compose(Stamp3, Stamp2); 52 | const S3 = Stamp2.compose(Stamp1, Stamp3); 53 | const S4 = Stamp2.compose(Stamp3, Stamp1); 54 | const S5 = Stamp3.compose(Stamp1, Stamp2); 55 | const S6 = Stamp3.compose(Stamp2, Stamp1); 56 | 57 | expect(S1({ x: 2 })).toStrictEqual({ x: 2 }); 58 | expect(S2({ x: 2 })).toStrictEqual({ x: 2 }); 59 | expect(S3({ x: 2 })).toStrictEqual({ x: 2 }); 60 | expect(S4()).toStrictEqual({ x: 1 }); 61 | expect(S5()).toStrictEqual({ x: 1 }); 62 | expect(S6()).toStrictEqual({ x: 1 }); 63 | }); 64 | 65 | it('overwrites metadata', function() { 66 | const Stamp = ArgOverProp.argOverProp('x') 67 | .argOverProp(['x']) 68 | .argOverProp({ x: 1 }); 69 | 70 | expect(Stamp({ x: 2 })).toStrictEqual({ x: 2 }); 71 | expect(Stamp()).toStrictEqual({ x: 1 }); 72 | }); 73 | 74 | it('de-duplicates property names in metadata', function() { 75 | const Base = ArgOverProp.argOverProp('dontTouch'); 76 | 77 | let Stamp = Base.argOverProp('x') 78 | .argOverProp(['x']) 79 | .argOverProp({ x: 1 }); 80 | expect(Stamp.compose.deepConfiguration.ArgOverProp).toHaveLength(2); 81 | 82 | Stamp = Stamp.compose(Base.argOverProp('x')); 83 | expect(Stamp.compose.deepConfiguration.ArgOverProp).toHaveLength(2); 84 | 85 | Stamp = Stamp.compose({ deepConfiguration: { ArgOverProp: ['x'] } }); 86 | expect(Stamp.compose.deepConfiguration.ArgOverProp).toHaveLength(2); 87 | }); 88 | 89 | it('initializer does not crash under any circumstances', function() { 90 | const badArgs = [0, 1, {}, [], NaN, function() {}, null, undefined]; 91 | const Stamp = ArgOverProp.argOverProp({ dontTouch: 42 }); 92 | badArgs.forEach(function(arg) { 93 | const instance = Stamp(arg); 94 | expect(instance.dontTouch).toBe(42); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /packages/arg-over-prop/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const core_1 = require("@stamp/core"); 8 | const is_1 = require("@stamp/is"); 9 | const { get, ownKeys, set } = Reflect; 10 | const initializer = function initializer(opts, ref) { 11 | if ((0, is_1.isObject)(opts)) { 12 | const { deepConfiguration } = ref.stamp.compose; 13 | const keysToAssign = deepConfiguration === null || deepConfiguration === void 0 ? void 0 : deepConfiguration.ArgOverProp; 14 | if (keysToAssign === null || keysToAssign === void 0 ? void 0 : keysToAssign.length) { 15 | keysToAssign.forEach((key) => { 16 | const incomingValue = get(opts, key); 17 | if (incomingValue !== undefined) { 18 | set(this, key, incomingValue); 19 | } 20 | }); 21 | } 22 | } 23 | }; 24 | const dedupe = (array) => [...new Set(array)]; 25 | /** 26 | * TODO 27 | */ 28 | const ArgOverProp = (0, compose_1.default)({ 29 | staticProperties: { 30 | argOverProp(...args) { 31 | let propNames = []; 32 | let defaultProps; 33 | args.forEach((arg) => { 34 | if ((0, is_1.isString)(arg)) { 35 | propNames.push(arg); 36 | } 37 | else if ((0, is_1.isArray)(arg)) { 38 | propNames = [...propNames, ...arg.filter(is_1.isString)]; 39 | } 40 | else if ((0, is_1.isObject)(arg)) { 41 | defaultProps = (0, core_1.assign)(defaultProps || {}, arg); 42 | propNames = [...propNames, ...ownKeys(arg)]; 43 | } 44 | }); 45 | const localStamp = ((this === null || this === void 0 ? void 0 : this.compose) ? this : ArgOverProp); 46 | return localStamp.compose({ 47 | deepConfiguration: { ArgOverProp: propNames }, 48 | properties: defaultProps, // default property values 49 | }); 50 | }, 51 | }, 52 | initializers: [initializer], 53 | composers: [ 54 | ((opts) => { 55 | const descriptor = opts.stamp.compose; 56 | const { initializers } = descriptor; 57 | // Always keep our initializer the first 58 | initializers.splice(initializers.indexOf(initializer), 1); 59 | initializers.unshift(initializer); 60 | const { deepConfiguration } = descriptor; 61 | const propNames = deepConfiguration === null || deepConfiguration === void 0 ? void 0 : deepConfiguration.ArgOverProp; 62 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 63 | if ((0, is_1.isArray)(propNames)) 64 | deepConfiguration.ArgOverProp = dedupe(propNames); 65 | }), 66 | ], 67 | }); 68 | exports.default = ArgOverProp; 69 | // For CommonJS default export support 70 | module.exports = ArgOverProp; 71 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: ArgOverProp }); 72 | -------------------------------------------------------------------------------- /packages/arg-over-prop/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { ComposeProperty, Composer, Descriptor, Initializer, PropertyMap, Stamp } from '@stamp/compose'; 2 | import { assign } from '@stamp/core'; 3 | import { isArray, isObject, isString } from '@stamp/is'; 4 | 5 | const { get, ownKeys, set } = Reflect; 6 | 7 | interface ArgOverPropDescriptor extends Descriptor { 8 | deepConfiguration?: PropertyMap & { ArgOverProp: PropertyKey[] }; 9 | } 10 | 11 | const initializer: Initializer = function initializer(opts, ref) { 12 | if (isObject(opts)) { 13 | const { deepConfiguration } = ref.stamp.compose as ArgOverPropDescriptor; 14 | const keysToAssign = deepConfiguration?.ArgOverProp; 15 | if (keysToAssign?.length) { 16 | keysToAssign.forEach((key) => { 17 | const incomingValue = get(opts, key); 18 | if (incomingValue !== undefined) { 19 | set(this, key, incomingValue); 20 | } 21 | }); 22 | } 23 | } 24 | }; 25 | 26 | const dedupe = (array: T[]): T[] => [...new Set(array)]; 27 | 28 | /** 29 | * TODO 30 | */ 31 | const ArgOverProp = compose({ 32 | staticProperties: { 33 | argOverProp(...args: unknown[]): Stamp { 34 | let propNames: PropertyKey[] = []; 35 | let defaultProps: PropertyMap | undefined; 36 | args.forEach((arg) => { 37 | if (isString(arg)) { 38 | propNames.push(arg); 39 | } else if (isArray(arg)) { 40 | propNames = [...propNames, ...arg.filter(isString)]; 41 | } else if (isObject(arg)) { 42 | defaultProps = assign(defaultProps || {}, arg); 43 | propNames = [...propNames, ...ownKeys(arg)]; 44 | } 45 | }); 46 | 47 | const localStamp = (this?.compose ? this : ArgOverProp) as Stamp; 48 | return localStamp.compose({ 49 | deepConfiguration: { ArgOverProp: propNames }, 50 | properties: defaultProps, // default property values 51 | }); 52 | }, 53 | }, 54 | initializers: [initializer], 55 | composers: [ 56 | ((opts) => { 57 | const descriptor = opts.stamp.compose; 58 | const { initializers } = descriptor as Required; 59 | // Always keep our initializer the first 60 | initializers.splice(initializers.indexOf(initializer), 1); 61 | initializers.unshift(initializer); 62 | 63 | const { deepConfiguration } = descriptor as ArgOverPropDescriptor; 64 | const propNames = deepConfiguration?.ArgOverProp; 65 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 66 | if (isArray(propNames)) deepConfiguration!.ArgOverProp = dedupe(propNames); 67 | }) as Composer, 68 | ], 69 | }); 70 | 71 | export default ArgOverProp; 72 | 73 | // For CommonJS default export support 74 | module.exports = ArgOverProp; 75 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: ArgOverProp }); 76 | -------------------------------------------------------------------------------- /packages/arg-over-prop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/arg-over-prop", 3 | "version": "1.0.4", 4 | "description": "Assign properties passed to the stamp factory", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/arg-over-prop" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2", 18 | "@stamp/core": "^1.0.1", 19 | "@stamp/is": "^1.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/arg-over-prop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/check-compose/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "jest/consistent-test-it": 0, 4 | "jest/expect-expect": 0, 5 | "jest/lowercase-name": 0, 6 | "jest/no-alias-methods": 0, 7 | "jest/no-commented-out-tests": 0, 8 | "jest/no-disabled-tests": 0, 9 | "jest/no-duplicate-hooks": 0, 10 | "jest/no-expect-resolves": 0, 11 | "jest/no-export": 0, 12 | "jest/no-focused-tests": 0, 13 | "jest/no-hooks": 0, 14 | "jest/no-identical-title": 0, 15 | "jest/no-if": 0, 16 | "jest/no-jasmine-globals": 0, 17 | "jest/no-jest-import": 0, 18 | "jest/no-large-snapshots": 0, 19 | "jest/no-mocks-import": 0, 20 | "jest/no-standalone-expect": 0, 21 | "jest/no-test-callback": 0, 22 | "jest/no-test-prefixes": 0, 23 | "jest/no-test-return-statement": 0, 24 | "jest/no-truthy-falsy": 0, 25 | "jest/no-try-expect": 0, 26 | "jest/prefer-called-with": 0, 27 | "jest/prefer-expect-assertions": 0, 28 | "jest/prefer-hooks-on-top": 0, 29 | "jest/prefer-inline-snapshots": 0, 30 | "jest/prefer-spy-on": 0, 31 | "jest/prefer-strict-equal": 0, 32 | "jest/prefer-to-be-null": 0, 33 | "jest/prefer-to-be-undefined": 0, 34 | "jest/prefer-to-contain": 0, 35 | "jest/prefer-to-have-length": 0, 36 | "jest/prefer-todo": 0, 37 | "jest/require-to-throw-message": 0, 38 | "jest/require-top-level-describe": 0, 39 | "jest/valid-describe": 0, 40 | "jest/valid-expect": 0, 41 | "jest/valid-expect-in-promise": 0, 42 | "jest/valid-title": 0 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/check-compose/README.md: -------------------------------------------------------------------------------- 1 | # check-compose 2 | Command line tool to test your ['compose' function implementation](https://github.com/stampit-org/stamp-specification) 3 | 4 | ## Install 5 | 6 | ```sh 7 | $ npm i -g @stamp/check-compose 8 | ``` 9 | 10 | ## Usage 11 | 12 | ### Command line 13 | 14 | ```sh 15 | $ check-compose path/to/compose.js 16 | ``` 17 | 18 | ### Programmatically 19 | 20 | ```js 21 | import checkCompose from 'check-compose'; 22 | checkCompose(myComposeFunction).then(({failures}) => { 23 | console.error(failures.join('\n')); 24 | }); 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/check-compose/assignment-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | 5 | module.exports = (compose) => { 6 | const assignmentProps = [ 7 | 'methods', 8 | 'properties', 9 | 'deepProperties', 10 | 'staticProperties', 11 | 'staticDeepProperties', 12 | 'configuration', 13 | 'deepConfiguration', 14 | ]; 15 | 16 | const symbol = Symbol.for('test'); 17 | 18 | const build = (num) => { 19 | const composable = () => {}; 20 | composable.compose = () => {}; 21 | 22 | assignmentProps.forEach((prop) => { 23 | composable.compose[prop] = { 24 | [num]: num, 25 | override: num, 26 | 27 | [symbol]: num, 28 | 29 | actualValue: null, 30 | get getter() { 31 | return this.actualValue; 32 | }, 33 | set setter(val) { 34 | this.actualValue = val; 35 | }, // jshint -W078 36 | get getterAndSetter() { 37 | return this.actualValue; 38 | }, 39 | set getterAndSetter(val) { 40 | this.actualValue = val; 41 | }, 42 | }; 43 | }); 44 | 45 | return composable; 46 | }; 47 | 48 | // Loop over each property that is copied by assignment and ensure 49 | // that copy and priority are implemented correctly. 50 | assignmentProps.forEach((prop) => { 51 | test(`${prop} assignment 1`, (assert) => { 52 | const subject = compose(build(1), build(2)); 53 | const descr = subject.compose; 54 | 55 | let actual = descr[prop][1]; 56 | let expected = 1; 57 | 58 | assert.equal(actual, expected, `${prop} should be copied by assignment from first argument`); 59 | 60 | actual = descr[prop][symbol]; 61 | expected = 2; 62 | 63 | assert.equal(actual, expected, `${prop} should be copied by assignment from first argument via Symbol`); 64 | 65 | assert.end(); 66 | }); 67 | 68 | test(`${prop} assignment 2`, (assert) => { 69 | const subject = compose(build(1), build(2)); 70 | const descr = subject.compose; 71 | 72 | let actual = descr[prop][2]; 73 | const expected = 2; 74 | 75 | assert.equal(actual, expected, `${prop} should be copied by assignment from 2nd argument`); 76 | 77 | actual = descr[prop][symbol]; 78 | 79 | assert.equal(actual, expected, `${prop} should be copied by assignment from 2nd argument via Symbol`); 80 | 81 | assert.end(); 82 | }); 83 | 84 | test(`${prop} assignment 3`, (assert) => { 85 | const subject = compose(build(1), build(2), build(3)); 86 | const descr = subject.compose; 87 | 88 | let actual = descr[prop][3]; 89 | const expected = 3; 90 | 91 | assert.equal(actual, expected, `${prop} should be copied by assignment from subsequent arguments`); 92 | 93 | actual = descr[prop][symbol]; 94 | 95 | assert.equal(actual, expected, `${prop} should be copied by assignment from subsequent arguments via Symbol`); 96 | 97 | assert.end(); 98 | }); 99 | 100 | test(`${prop} assignment priority`, (assert) => { 101 | const subject = compose(build(1), build(2)); 102 | const descr = subject.compose; 103 | 104 | let actual = descr[prop].override; 105 | const expected = 2; 106 | 107 | assert.equal(actual, expected, `${prop} should be copied by assignment with last-in priority`); 108 | 109 | actual = descr[prop][symbol]; 110 | 111 | assert.equal(actual, expected, `${prop} should be copied by assignment with last-in priority via Symbol`); 112 | 113 | assert.end(); 114 | }); 115 | 116 | test(`${prop} getters copying`, (assert) => { 117 | const subject = compose(build(1), build(2)); 118 | const descr = subject.compose; 119 | 120 | const actual = descr[prop].getter; 121 | const expected = descr[prop].actualValue; 122 | 123 | assert.equal(actual, expected, `${prop} getter should be copied`); 124 | 125 | assert.end(); 126 | }); 127 | 128 | test(`${prop} setters copying`, (assert) => { 129 | const subject = compose(build(1), build(2)); 130 | const descr = subject.compose; 131 | 132 | descr[prop].setter = 4; 133 | const actual = descr[prop].actualValue; 134 | const expected = 4; 135 | 136 | assert.equal(actual, expected, `${prop} setter should be copied`); 137 | 138 | assert.end(); 139 | }); 140 | 141 | test(`${prop} getter+setter copying`, (assert) => { 142 | const subject = compose(build(1), build(2)); 143 | const descr = subject.compose; 144 | 145 | let actual = descr[prop].getterAndSetter; 146 | let expected = descr[prop].actualValue; 147 | 148 | assert.equal(actual, expected, `${prop} getter should be copied`); 149 | 150 | descr[prop].getterAndSetter = 4; 151 | actual = descr[prop].actualValue; 152 | expected = 4; 153 | 154 | assert.equal(actual, expected, `${prop} setter after getter should be copied`); 155 | 156 | descr[prop].getterAndSetter = 5; 157 | actual = descr[prop].getterAndSetter; 158 | expected = 5; 159 | 160 | assert.equal(actual, expected, `${prop} getter should return setter's value`); 161 | 162 | assert.end(); 163 | }); 164 | }); 165 | }; 166 | -------------------------------------------------------------------------------- /packages/check-compose/bin/check-compose.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable global-require */ 4 | /* eslint-disable no-console */ 5 | /* eslint-disable no-nested-ternary */ 6 | /* eslint-disable no-process-exit */ 7 | /* eslint-disable import/no-dynamic-require */ 8 | 9 | 'use strict'; 10 | 11 | (() => { 12 | const path = require('path'); 13 | const composeImplementationFiles = process.argv.slice(2); 14 | if (!composeImplementationFiles.length) { 15 | console.log(`usage: ${path.basename(process.argv[1])} COMPOSE_FILE_NAMES...`); 16 | process.exit(1); 17 | // return; 18 | } 19 | 20 | const importedImplementations = composeImplementationFiles.map((file) => { 21 | try { 22 | return require(file); 23 | } catch (_err) { 24 | try { 25 | // eslint-disable-next-line no-param-reassign 26 | file = path.resolve(process.cwd(), file); 27 | return require(file); 28 | } catch (err) { 29 | console.error(`${err}\nFailed to read JavaScript file ${file}`); 30 | process.exit(1); 31 | } 32 | } 33 | }); 34 | 35 | importedImplementations.forEach((imported, index) => { 36 | const compose = 37 | typeof imported === 'function' 38 | ? imported 39 | : typeof imported.default === 'function' 40 | ? imported.default 41 | : typeof imported.compose === 'function' 42 | ? imported.compose 43 | : undefined; 44 | if (!compose) { 45 | console.error(`The file ${composeImplementationFiles[index]} should export the "compose" function.`); 46 | process.exit(1); 47 | // return; 48 | } 49 | 50 | require('../')(compose).then((result) => { 51 | const { failures } = result; 52 | if (failures && failures.length > 0) { 53 | console.error(require('prettyjson').render(failures)); 54 | process.exit(1); 55 | } 56 | }); 57 | }); 58 | })(); 59 | -------------------------------------------------------------------------------- /packages/check-compose/compose-basic-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | 5 | module.exports = (compose) => { 6 | test('compose function', (assert) => { 7 | const actual = typeof compose; 8 | const expected = 'function'; 9 | 10 | assert.equal(actual, expected, 'compose should be a function.'); 11 | 12 | assert.end(); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/check-compose/compose-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = (compose) => { 7 | test('compose ignores non objects', (assert) => { 8 | const stamp = compose(0, 'a', null, undefined, {}, NaN, /regexp/); 9 | const subject = _.values(stamp.compose).filter(_.negate(_.isEmpty)); 10 | const expected = _.values(compose().compose); 11 | 12 | assert.deepEqual(subject, expected, 'should not add any descriptor data'); 13 | 14 | assert.end(); 15 | }); 16 | 17 | test('compose in order', (assert) => { 18 | const initOrder = []; 19 | const getInitDescriptor = (value) => { 20 | return { 21 | initializers: [ 22 | () => { 23 | initOrder.push(value); 24 | }, 25 | ], 26 | }; 27 | }; 28 | 29 | const stamp = compose( 30 | compose(getInitDescriptor(0)), 31 | compose(getInitDescriptor(1), getInitDescriptor(2)).compose(getInitDescriptor(3), getInitDescriptor(4)), 32 | getInitDescriptor(5) 33 | ); 34 | stamp(); 35 | const expected = [0, 1, 2, 3, 4, 5]; 36 | 37 | assert.deepEqual(initOrder, expected, 'should compose in proper order'); 38 | 39 | assert.end(); 40 | }); 41 | 42 | test('compose is detachable', (assert) => { 43 | const detachedCompose = compose().compose; 44 | 45 | assert.notEqual(compose, detachedCompose, 'stamp .compose function must be a different object to "compose"'); 46 | 47 | assert.end(); 48 | }); 49 | 50 | test('detached compose does not inherit previous descriptor', (assert) => { 51 | const detachedCompose = compose({ properties: { foo: 1 } }).compose; 52 | const obj = detachedCompose()(); 53 | let expected; 54 | 55 | assert.equal(obj.foo, expected, 'detached compose method should not inherit parent descriptor data'); 56 | 57 | assert.end(); 58 | }); 59 | 60 | test('compose is replaceable', (assert) => { 61 | let counter = 0; 62 | 63 | function newCompose(...args) { 64 | counter += 1; 65 | return compose({ staticProperties: { compose: newCompose } }, this, args); 66 | } 67 | 68 | newCompose() 69 | .compose() 70 | .compose(); 71 | const expected = 3; 72 | 73 | assert.equal(counter, expected, 'should inherit new compose function'); 74 | 75 | assert.end(); 76 | }); 77 | 78 | test('replaced compose method is always a new object', (assert) => { 79 | function newCompose(...args) { 80 | return compose({ staticProperties: { compose: newCompose } }, this, args); 81 | } 82 | 83 | const stamp1 = newCompose(); 84 | const compose1 = stamp1.compose; 85 | const stamp2 = stamp1.compose(); 86 | const compose2 = stamp2.compose; 87 | 88 | assert.notEqual(compose1, compose2, 'should be different objects'); 89 | 90 | assert.end(); 91 | }); 92 | 93 | test('replaced compose method is always a function', (assert) => { 94 | function newCompose(...args) { 95 | return compose({ staticProperties: { compose: 'rubbish' } }, this, args); 96 | } 97 | 98 | const overridenCompose = newCompose().compose().compose; 99 | const actual = _.isFunction(overridenCompose); 100 | const expected = true; 101 | 102 | assert.equal(actual, expected, 'should be a function'); 103 | 104 | assert.end(); 105 | }); 106 | 107 | test('broken stamp', (assert) => { 108 | const brokenStamp = compose(); 109 | delete brokenStamp.compose; 110 | 111 | const instance = brokenStamp(); 112 | assert.ok(instance); 113 | 114 | assert.end(); 115 | }); 116 | }; 117 | -------------------------------------------------------------------------------- /packages/check-compose/descriptor-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = (compose) => { 7 | test('compose function pojo (Plain Old JavaScript Object)', (nest) => { 8 | const objDescriptors = [ 9 | 'properties', 10 | 'deepProperties', 11 | 'staticProperties', 12 | 'staticDeepProperties', 13 | 'propertyDescriptors', 14 | 'staticPropertyDescriptors', 15 | 'configuration', 16 | ]; 17 | 18 | objDescriptors.forEach((descriptorName) => { 19 | nest.test(`...with pojo descriptor.${descriptorName}`, (assert) => { 20 | const descriptor = {}; 21 | descriptor[descriptorName] = { 22 | a: { 23 | b: 'b', 24 | }, 25 | }; 26 | 27 | const metadata = compose(descriptor).compose[descriptorName]; 28 | assert.ok(metadata); 29 | const actual = metadata.a; 30 | const expected = { b: 'b' }; 31 | 32 | assert.deepEqual(actual, expected, `should create ${descriptorName} descriptor`); 33 | 34 | assert.end(); 35 | }); 36 | }); 37 | }); 38 | 39 | test('compose function pojo', (nest) => { 40 | nest.test('...with pojo descriptor.methods', (assert) => { 41 | const a = function a() { 42 | return 'a'; 43 | }; 44 | 45 | const actual = Object.getPrototypeOf( 46 | compose({ 47 | methods: { a }, 48 | })() 49 | ); 50 | 51 | const expected = { a }; 52 | 53 | assert.deepEqual(actual, expected, 'should create methods descriptor'); 54 | 55 | assert.end(); 56 | }); 57 | 58 | nest.test('...with pojo descriptor.initializers', (assert) => { 59 | const a = function a() { 60 | return 'a'; 61 | }; 62 | 63 | const actual = compose({ 64 | initializers: [a], 65 | }).compose.initializers; 66 | 67 | assert.ok(_.includes(actual, a), 'should have initializers descriptor'); 68 | 69 | assert.end(); 70 | }); 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /packages/check-compose/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-dynamic-require */ 3 | 4 | 'use strict'; 5 | 6 | import fs from 'fs'; 7 | import ttest from 'tape'; 8 | 9 | function checkCompose(compose: CM) { 10 | if (typeof compose !== 'function') throw new Error('"compose" must be a function'); 11 | 12 | const files = fs.readdirSync(__dirname).filter(RegExp.prototype.test.bind(/.+-tests\.js$/)); 13 | 14 | const failures: any[] = []; 15 | 16 | return new Promise((resolve) => { 17 | ttest 18 | .createStream({ objectMode: true }) 19 | .on('data', (assert) => { 20 | if (assert.error) failures.push(assert); 21 | }) 22 | .on('end', () => { 23 | resolve({ failures }); 24 | }); 25 | 26 | files.forEach((file) => { 27 | require(`./${file}`)(compose); 28 | }); 29 | }); 30 | }; 31 | 32 | export default checkCompose; 33 | 34 | module.exports = checkCompose; 35 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: checkCompose }); 36 | -------------------------------------------------------------------------------- /packages/check-compose/instance-replacement-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | 5 | module.exports = (compose) => { 6 | [0, 1, null, NaN, 'string', true, false].forEach((obj) => { 7 | test(`initializer returns ${obj}`, (assert) => { 8 | compose({ 9 | initializers: [ 10 | () => { 11 | return obj; 12 | }, 13 | (options, o) => { 14 | const { instance } = o; 15 | const actual = typeof instance; 16 | const expected = typeof obj; 17 | 18 | assert.equal(actual, expected, 'initializer return value should replace instance'); 19 | 20 | assert.end(); 21 | }, 22 | ], 23 | })(); 24 | }); 25 | }); 26 | 27 | test('initializer returns undefined', (assert) => { 28 | compose({ 29 | initializers: [ 30 | () => { 31 | return undefined; 32 | }, 33 | (options, o) => { 34 | const { instance } = o; 35 | const actual = typeof instance; 36 | const expected = 'object'; 37 | 38 | assert.equal(actual, expected, 'initializer return value should not replace instance'); 39 | 40 | assert.end(); 41 | }, 42 | ], 43 | })(); 44 | }); 45 | 46 | test('instance replacement', (assert) => { 47 | const message = 'instance replaced'; 48 | const newInstance = { 49 | message, 50 | }; 51 | 52 | const obj = compose({ 53 | initializers: [ 54 | () => { 55 | return newInstance; 56 | }, 57 | ], 58 | })(); 59 | 60 | const actual = obj.message; 61 | const expected = message; 62 | 63 | assert.equal(actual, expected, 'the replaced instance value should be returned from the stamp'); 64 | 65 | assert.end(); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /packages/check-compose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/check-compose", 3 | "version": "1.0.3", 4 | "license": "MIT", 5 | "description": "Command line tool to test your 'compose' function implementation", 6 | "repository": "https://github.com/stampit-org/stamp/tree/master/packages/check-compose", 7 | "bin": { 8 | "check-compose": "./bin/check-compose.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "link": "npm run build && lerna link" 13 | }, 14 | "dependencies": { 15 | "lodash": "^4.17.15", 16 | "prettyjson": "^1.2.1", 17 | "tape": "^4.11.0" 18 | }, 19 | "devDependencies": { 20 | "@types/tape": "^4.13.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/check-compose/priority-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = (compose) => { 7 | const buildDescriptor = (obj) => { 8 | return _.assign( 9 | {}, 10 | { 11 | properties: { 12 | a: 'props', 13 | b: 'props', 14 | }, 15 | }, 16 | obj 17 | ); 18 | }; 19 | 20 | test('compose override priorities', (nest) => { 21 | nest.test('...with instance', (assert) => { 22 | const stamp = compose(buildDescriptor()); 23 | const actual = stamp({ 24 | instance: { 25 | a: 'instance', 26 | }, 27 | }).a; 28 | const expected = 'props'; 29 | 30 | assert.equal(actual, expected, 'properties should override instance props'); 31 | 32 | assert.end(); 33 | }); 34 | 35 | nest.test('...with deepProperties', (assert) => { 36 | const stamp = compose( 37 | buildDescriptor({ 38 | deepProperties: { 39 | a: 'deepProps', 40 | }, 41 | }) 42 | ); 43 | const actual = stamp().a; 44 | const expected = 'props'; 45 | 46 | assert.equal(actual, expected, 'shallow properties should override deep properties'); 47 | 48 | assert.end(); 49 | }); 50 | 51 | nest.test('...with descriptors', (assert) => { 52 | const stamp = compose( 53 | buildDescriptor({ 54 | propertyDescriptors: { 55 | b: { 56 | value: 'propertyDescriptors', 57 | }, 58 | }, 59 | }) 60 | ); 61 | 62 | const actual = stamp().b; 63 | const expected = 'propertyDescriptors'; 64 | 65 | assert.equal(actual, expected, 'descriptors should override shallow properties'); 66 | assert.end(); 67 | }); 68 | 69 | nest.test('...with instance & deepProperties', (assert) => { 70 | const stamp = compose( 71 | buildDescriptor({ 72 | deepProperties: { 73 | c: 'deep', 74 | }, 75 | }) 76 | ); 77 | const actual = stamp({ 78 | instance: { 79 | c: 'instance', 80 | }, 81 | }).c; 82 | const expected = 'deep'; 83 | 84 | assert.equal(actual, expected, 'deepProperties should override instance props'); 85 | 86 | assert.end(); 87 | }); 88 | 89 | nest.test('...with staticProperties', (assert) => { 90 | const stamp = compose({ 91 | staticDeepProperties: { 92 | d: 'deep', 93 | }, 94 | staticProperties: { 95 | d: 'staticProps', 96 | }, 97 | }); 98 | const actual = stamp.d; 99 | const expected = 'staticProps'; 100 | 101 | assert.equal(actual, expected, 'staticProperties should override staticDeepProperties'); 102 | assert.end(); 103 | }); 104 | }); 105 | }; 106 | -------------------------------------------------------------------------------- /packages/check-compose/property-descriptor-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = (compose) => { 7 | const createDescriptors = () => { 8 | const a = { 9 | value: 'a', 10 | writable: false, 11 | configurable: false, 12 | enumerable: false, 13 | }; 14 | const b = _.assign({}, a); 15 | const descriptors = { a, b }; 16 | return descriptors; 17 | }; 18 | 19 | test('stamp', (nest) => { 20 | nest.test('...with propertyDescriptors', (assert) => { 21 | const descriptors = createDescriptors(); 22 | const { b } = descriptors; 23 | 24 | const obj = compose({ 25 | propertyDescriptors: _.assign({}, descriptors), 26 | })(); 27 | 28 | const actual = Object.getOwnPropertyDescriptor(obj, 'b'); 29 | const expected = _.assign({}, b); 30 | 31 | assert.deepEqual(actual, expected, 'should assign propertyDescriptors to instances'); 32 | 33 | assert.end(); 34 | }); 35 | 36 | nest.test('...with malformed propertyDescriptors', (assert) => { 37 | [0, 'a', null, undefined, {}, NaN, /regexp/].forEach((propertyValue) => { 38 | const actual = compose({ 39 | propertyDescriptors: propertyValue, 40 | })(); 41 | const expected = compose()(); 42 | 43 | assert.deepEqual(actual, expected, 'should not any properties instances'); 44 | }); 45 | 46 | assert.end(); 47 | }); 48 | 49 | nest.test('...with malformed staticPropertyDescriptors', (assert) => { 50 | [0, 'a', null, undefined, {}, NaN, /regexp/].forEach((propertyValue) => { 51 | const stamp = compose({ 52 | staticPropertyDescriptors: propertyValue, 53 | }); 54 | const actual = _.values(stamp.compose).filter((value) => { 55 | return !_.isEmpty(value); 56 | }); 57 | const expected = _.values(compose().compose); 58 | 59 | assert.deepEqual(actual, expected, 'should not add any descriptor data'); 60 | }); 61 | 62 | assert.end(); 63 | }); 64 | 65 | nest.test('...with propertyDescriptors and existing prop conflict', (assert) => { 66 | const descriptors = createDescriptors(); 67 | 68 | const obj = compose( 69 | { 70 | properties: { 71 | a: 'existing prop', 72 | }, 73 | }, 74 | { 75 | propertyDescriptors: _.assign({}, descriptors), 76 | } 77 | )(); 78 | 79 | const actual = obj.a; 80 | const expected = 'a'; 81 | 82 | assert.deepEqual(actual, expected, 'should assign propertyDescriptors to instances & override existing prop'); 83 | 84 | assert.end(); 85 | }); 86 | 87 | nest.test('...with staticPropertyDescriptors', (assert) => { 88 | const descriptors = createDescriptors(); 89 | const { b } = descriptors; 90 | 91 | const stamp = compose({ 92 | staticPropertyDescriptors: _.assign({}, descriptors), 93 | }); 94 | 95 | const actual = Object.getOwnPropertyDescriptor(stamp, 'b'); 96 | const expected = _.assign({}, b); 97 | 98 | assert.deepEqual(actual, expected, 'should assign staticPropertyDescriptors to stamp'); 99 | 100 | assert.end(); 101 | }); 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /packages/check-compose/property-safety-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | 5 | module.exports = (compose) => { 6 | test('Deep properties', (nest) => { 7 | nest.test('...should be cloned for descriptors', (assert) => { 8 | const deepInstance = { e: 'deep' }; 9 | const stamp = compose({ 10 | deepProperties: { 11 | obj: deepInstance, 12 | }, 13 | }); 14 | const actual = stamp.compose.deepProperties.obj; 15 | 16 | assert.notEqual(actual, deepInstance, 'deepProperties should not be assigned between descriptors'); 17 | assert.end(); 18 | }); 19 | 20 | nest.test('...should be cloned for instances', (assert) => { 21 | const stamp = compose({ 22 | deepProperties: { 23 | obj: { e: 'deep' }, 24 | }, 25 | }); 26 | const notExpected = stamp.compose.deepProperties.obj; 27 | const actual = stamp().obj; 28 | 29 | assert.notEqual(actual, notExpected, 'deepProperties should not be assigned from descriptor to object instance'); 30 | assert.end(); 31 | }); 32 | }); 33 | 34 | test('Deep static properties', (nest) => { 35 | nest.test('...should be cloned for descriptors', (assert) => { 36 | const deepInstance = { e: 'deep' }; 37 | const stamp = compose({ 38 | staticDeepProperties: { 39 | obj: deepInstance, 40 | }, 41 | }); 42 | const actual = stamp.compose.staticDeepProperties.obj; 43 | 44 | assert.notEqual(actual, deepInstance, 'staticDeepProperties should not be assigned between descriptors'); 45 | assert.end(); 46 | }); 47 | 48 | nest.test('...should be cloned for new stamps', (assert) => { 49 | const stamp = compose({ 50 | staticDeepProperties: { 51 | obj: { e: 'deep' }, 52 | }, 53 | }); 54 | const notExpected = stamp.compose.staticDeepProperties.obj; 55 | const actual = stamp.obj; 56 | 57 | assert.notEqual(actual, notExpected, 'staticDeepProperties should not be assigned from descriptor to stamp'); 58 | assert.end(); 59 | }); 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /packages/check-compose/stamp-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = (compose) => { 7 | const build = (prop, key, val) => { 8 | // eslint-disable-next-line no-param-reassign 9 | val = val || key; 10 | const composable = () => {}; 11 | composable.compose = () => {}; 12 | 13 | composable.compose[prop] = {}; 14 | composable.compose[prop][val] = val; 15 | 16 | return composable; 17 | }; 18 | 19 | test('compose()', (assert) => { 20 | const actual = typeof compose(); 21 | const expected = 'function'; 22 | 23 | assert.equal(actual, expected, 'compose() should return a function'); 24 | 25 | assert.end(); 26 | }); 27 | 28 | test('Stamp', (nest) => { 29 | nest.test('...with no arguments', (assert) => { 30 | const actual = typeof compose()(); 31 | const expected = 'object'; 32 | 33 | assert.equal(actual, expected, 'should produce an object instance'); 34 | 35 | assert.end(); 36 | }); 37 | 38 | nest.test('...with broken descriptor', (assert) => { 39 | const stamp = compose(); 40 | stamp.compose = null; 41 | 42 | const expected = {}; 43 | const actual = stamp(); 44 | 45 | assert.deepEqual(actual, expected, 'should ignore non function initializers'); 46 | 47 | assert.end(); 48 | }); 49 | 50 | nest.test('should allow late descriptor edition', (assert) => { 51 | const stamp = compose(); 52 | stamp.compose.properties = { foo: 'exists' }; 53 | 54 | const expected = { foo: 'exists' }; 55 | const actual = stamp(); 56 | 57 | assert.deepEqual(actual, expected, 'should allow late addition of metadata'); 58 | 59 | assert.end(); 60 | }); 61 | }); 62 | 63 | test('Stamp assignments', (nest) => { 64 | nest.test('...with properties', (assert) => { 65 | const composable = () => {}; 66 | composable.compose = () => {}; 67 | composable.compose.properties = { 68 | a: 'a', 69 | b: 'b', 70 | }; 71 | const stamp = compose(composable); 72 | 73 | const expected = { 74 | a: 'a', 75 | b: 'b', 76 | }; 77 | const actual = _.pick(stamp(), _.keys(expected)); 78 | 79 | assert.deepEqual(actual, expected, 'should create properties'); 80 | 81 | assert.end(); 82 | }); 83 | }); 84 | 85 | test('Stamp.compose()', (nest) => { 86 | nest.test('...type', (assert) => { 87 | const actual = typeof compose().compose; 88 | const expected = 'function'; 89 | 90 | assert.equal(actual, expected, 'should be a function'); 91 | 92 | assert.end(); 93 | }); 94 | 95 | nest.test('...with no arguments', (assert) => { 96 | const actual = typeof compose().compose().compose; 97 | const expected = 'function'; 98 | 99 | assert.equal(actual, expected, 'should return a new stamp'); 100 | 101 | assert.end(); 102 | }); 103 | 104 | nest.test('...with base defaults', (assert) => { 105 | const stamp1 = compose(build('properties', 'a')); 106 | const stamp2 = compose(build('properties', 'b')); 107 | const finalStamp = stamp1.compose(stamp2); 108 | 109 | const expected = { 110 | a: 'a', 111 | b: 'b', 112 | }; 113 | const actual = _.pick(finalStamp(), _.keys(expected)); 114 | 115 | assert.deepEqual(actual, expected, 'should use Stamp as base composable'); 116 | 117 | assert.end(); 118 | }); 119 | }); 120 | }; 121 | -------------------------------------------------------------------------------- /packages/check-compose/static-properties-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const test = require('tape'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = (compose) => { 7 | test('compose static properties with POJO', (assert) => { 8 | const expected = { 9 | a: 'a', 10 | b: 'b', 11 | }; 12 | 13 | const { staticProperties } = compose( 14 | { 15 | staticProperties: { 16 | a: 'a', 17 | }, 18 | }, 19 | { 20 | staticProperties: { 21 | b: 'b', 22 | }, 23 | } 24 | ).compose; 25 | const actual = _.pick(staticProperties, _.keys(expected)); 26 | 27 | assert.deepEqual(actual, expected, 'should compose staticProperties into descriptor'); 28 | 29 | assert.end(); 30 | }); 31 | 32 | test('compose static properties with stamp', (assert) => { 33 | const expected = compose({ 34 | staticProperties: { 35 | a: 'a', 36 | b: 'b', 37 | }, 38 | }).compose.staticProperties; 39 | 40 | const actual = compose( 41 | { 42 | staticProperties: { 43 | a: 'a', 44 | }, 45 | }, 46 | { 47 | staticProperties: { 48 | b: 'b', 49 | }, 50 | } 51 | ).compose.staticProperties; 52 | 53 | assert.deepEqual(actual, expected, 'should compose staticProperties into descriptor'); 54 | 55 | assert.end(); 56 | }); 57 | 58 | test('compose static deep properties with POJO', (assert) => { 59 | const expected = { 60 | a: 'a', 61 | b: 'b', 62 | }; 63 | 64 | const { staticDeepProperties } = compose( 65 | { 66 | staticDeepProperties: { 67 | a: 'a', 68 | }, 69 | }, 70 | { 71 | staticDeepProperties: { 72 | b: 'b', 73 | }, 74 | } 75 | ).compose; 76 | const actual = _.pick(staticDeepProperties, _.keys(expected)); 77 | 78 | assert.deepEqual(actual, expected, 'should compose staticDeepProperties into descriptor'); 79 | 80 | assert.end(); 81 | }); 82 | 83 | test('compose static deep properties with stamp', (assert) => { 84 | const expected = compose({ 85 | staticDeepProperties: { 86 | a: 'a', 87 | b: 'b', 88 | }, 89 | }).compose.staticDeepProperties; 90 | 91 | const actual = compose( 92 | { 93 | staticDeepProperties: { 94 | a: 'a', 95 | }, 96 | }, 97 | { 98 | staticDeepProperties: { 99 | b: 'b', 100 | }, 101 | } 102 | ).compose.staticDeepProperties; 103 | 104 | assert.deepEqual(actual, expected, 'should compose staticDeepProperties into descriptor'); 105 | 106 | assert.end(); 107 | }); 108 | }; 109 | -------------------------------------------------------------------------------- /packages/collision/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/collision/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/collision 2 | 3 | _Controls collision behavior: forbid or defer_ 4 | 5 | This stamp (aka behavior) will check if there are any conflicts on every `compose` call. 6 | Throws an `Error` in case of a forbidden collision or ambiguous setup. 7 | 8 | ## Usage 9 | 10 | ```js 11 | import Collision from '@stamp/collision'; 12 | 13 | const ForbidRedrawCollision = Collision.collisionSetup({forbid: ['redraw']}); 14 | ``` 15 | 16 | Or if you don't want to import the stamp you can import only the method: 17 | ```js 18 | import {collisionSetup} from '@stamp/collision'; 19 | const ForbidRedrawCollision = collisionSetup({forbid: ['redraw']}); 20 | ``` 21 | 22 | 23 | The `defer` collects same named methods and wraps them into a single method. 24 | ```js 25 | import Collision from '@stamp/collision'; 26 | 27 | import {Border, Button, Graph} from './drawable/primitives'; 28 | 29 | const UiComponent = Collision.collisionSetup({defer: ['draw']}) 30 | .compose(Border, Button, Graph); 31 | 32 | const component = UiComponent(); 33 | component.draw(); // will draw() all three primitives 34 | ``` 35 | 36 | ## API 37 | 38 | ### Static methods 39 | 40 | #### collisionSetup 41 | Forbid or Defer an exclusive method 42 | `stamp.collisionSetup({forbid: ['methodName1'], defer: ['methodName2']}) -> Stamp` 43 | 44 | #### collisionProtectAnyMethod 45 | Forbid any collisions, excluding those allowed 46 | `stamp.collisionProtectAnyMethod({allow: ['methoName']}) -> Stamp` 47 | 48 | #### collisionSettingsReset 49 | Remove any Collision settings from the stamp 50 | `stamp.collisionSettingsReset() -> Stamp` 51 | 52 | ## Example 53 | 54 | See the comments in the code: 55 | ```js 56 | import compose from '@stamp/compose'; 57 | import Collision from '@stamp/collision'; 58 | import Privatize from '@stamp/privatize'; 59 | 60 | // General purpose behavior to defer "draw()" method collisions 61 | const DeferDraw = Collision.collisionSetup({defer: ['draw']}); 62 | 63 | const Border = compose(DeferDraw, { 64 | methods: { 65 | draw: jest.fn() // Spy function 66 | } 67 | }); 68 | const Button = compose(DeferDraw, { 69 | methods: { 70 | draw: jest.fn() // Spy function 71 | } 72 | }); 73 | 74 | // General purpose behavior to privatize the "draw()" method 75 | const PrivateDraw = Privatize.privatizeMethods('draw'); 76 | 77 | // General purpose behavior to forbid the "redraw()" method collision 78 | const ForbidRedrawCollision = Collision.collisionSetup({forbid: ['redraw']}); 79 | 80 | // The aggregating component 81 | const ModalDialog = compose(PrivateDraw) // privatize the "draw()" method 82 | .compose(Border, Button) // modal will consist of Border and Button 83 | .compose(ForbidRedrawCollision) // there can be only one "redraw()" method 84 | .compose({ 85 | methods: { 86 | // the public method which calls the deferred private methods 87 | redraw(int) { 88 | this.draw(int); 89 | } 90 | } 91 | }); 92 | 93 | // Creating an instance of the stamp 94 | const dialog = ModalDialog(); 95 | 96 | // Check if the "draw()" method is actually privatized 97 | expect(dialog.draw).toBeUndefined(); 98 | 99 | // Calling the public method, which calls the deferred private methods 100 | dialog.redraw(42); 101 | 102 | // Check if the spy "draw()" deferred functions were actually invoked 103 | expect(Border.compose.methods.draw).toBeCalledWith(42); 104 | expect(Button.compose.methods.draw).toBeCalledWith(42); 105 | 106 | // Make sure the ModalDialog throws every time on the "redraw()" collisions 107 | const HaveRedraw = compose({methods: {redraw() {}}}) 108 | expect(() => compose(ModalDialog, HaveRedraw)).toThrow(); 109 | ``` 110 | -------------------------------------------------------------------------------- /packages/collision/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/collision", 3 | "version": "1.1.0", 4 | "description": "Detect and manipulate method collisions", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/collision" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2", 18 | "@stamp/core": "^1.0.1", 19 | "@stamp/is": "^1.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/collision/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/compose/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/compose/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/compose 2 | 3 | _Implementation of the stamp `compose` [specification](https://github.com/stampit-org/stamp-specification)_ 4 | 5 | ```js 6 | var compose = require('@stamp/compose'); 7 | // or 8 | import compose from '@stamp/compose'; 9 | ``` 10 | -------------------------------------------------------------------------------- /packages/compose/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable jest/expect-expect */ 3 | /* eslint-disable jest/no-test-return-statement */ 4 | /* eslint-disable node/no-unpublished-require */ 5 | 6 | 'use strict'; 7 | 8 | import checkCompose from '@stamp/check-compose'; 9 | import { ComposeMethod } from '..'; 10 | 11 | describe('@stamp/compose', function () { 12 | it('passes official tests', async () => { 13 | return import('..') 14 | .then(module => { 15 | if (!module) { 16 | throw new Error('Module could not be imported') 17 | } 18 | const compose = module.default; 19 | return checkCompose(compose).then(result => { 20 | const { failures } = result as { failures?: any[] }; 21 | if (failures && failures.length > 0) { 22 | const errorString = failures 23 | .map(function (f) { 24 | return JSON.stringify(f); 25 | }) 26 | .join('\n'); 27 | throw new Error(errorString); 28 | } 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/compose/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const core_1 = require("@stamp/core"); 4 | const is_1 = require("@stamp/is"); 5 | const { defineProperties } = Object; 6 | const { get, set } = Reflect; 7 | const createFactory = () => { 8 | return function Stamp(options = {}, ...moreArgs) { 9 | const descriptor = Stamp.compose || {}; 10 | const { methods, properties, deepProperties, propertyDescriptors, initializers } = descriptor; 11 | // Next line was optimized for most JS VMs. Please, be careful here! 12 | let instance = { __proto__: methods }; 13 | // let obj = {}; 14 | // if (methods) setPrototypeOf(obj, methods); 15 | (0, core_1.merge)(instance, deepProperties); 16 | (0, core_1.assign)(instance, properties); 17 | defineProperties(instance, propertyDescriptors || {}); 18 | if (initializers && initializers.length > 0) { 19 | let returnedValue; 20 | const args = [options, ...moreArgs]; 21 | for (const initializer of initializers) { 22 | if ((0, is_1.isFunction)(initializer)) { 23 | returnedValue = initializer.call(instance, options, { 24 | instance, 25 | stamp: Stamp, 26 | args, 27 | }); 28 | if (returnedValue !== undefined) 29 | instance = returnedValue; 30 | } 31 | } 32 | } 33 | return instance; 34 | }; 35 | }; 36 | const createStamp = (descriptor, composeFunction) => { 37 | const Stamp = createFactory(); 38 | const { staticDeepProperties, staticProperties, staticPropertyDescriptors } = descriptor; 39 | if (staticDeepProperties) 40 | (0, core_1.merge)(Stamp, staticDeepProperties); 41 | if (staticProperties) 42 | (0, core_1.assign)(Stamp, staticProperties); 43 | if (staticPropertyDescriptors) 44 | defineProperties(Stamp, staticPropertyDescriptors); 45 | const composeImplementation = (0, is_1.isFunction)(Stamp.compose) ? Stamp.compose : composeFunction; 46 | Stamp.compose = function _compose(...args) { 47 | return composeImplementation.apply(this, args); 48 | }; 49 | (0, core_1.assign)(Stamp.compose, descriptor); 50 | return Stamp; 51 | }; 52 | const concatAssignFunctions = (dstObject, srcArray, propName) => { 53 | if ((0, is_1.isArray)(srcArray)) { 54 | const deduped = new Set(get(dstObject, propName) || []); 55 | for (const fn of srcArray) { 56 | if ((0, is_1.isFunction)(fn)) 57 | deduped.add(fn); 58 | } 59 | set(dstObject, propName, [...deduped]); 60 | } 61 | }; 62 | const combineProperties = (dstObject, srcObject, propName, action) => { 63 | const srcValue = get(srcObject, propName); 64 | if ((0, is_1.isObject)(srcValue)) { 65 | if (!(0, is_1.isObject)(get(dstObject, propName))) 66 | set(dstObject, propName, {}); 67 | action(get(dstObject, propName), srcValue); 68 | } 69 | }; 70 | const deepMergeAssign = (dstObject, srcObject, propName) => combineProperties(dstObject, srcObject, propName, core_1.merge); 71 | const mergeAssign = (dstObject, srcObject, propName) => combineProperties(dstObject, srcObject, propName, core_1.assign); 72 | const mergeComposable = (dstDescriptor, srcComposable) => { 73 | const srcDescriptor = (srcComposable === null || srcComposable === void 0 ? void 0 : srcComposable.compose) || srcComposable; 74 | mergeAssign(dstDescriptor, srcDescriptor, 'methods'); 75 | mergeAssign(dstDescriptor, srcDescriptor, 'properties'); 76 | deepMergeAssign(dstDescriptor, srcDescriptor, 'deepProperties'); 77 | mergeAssign(dstDescriptor, srcDescriptor, 'propertyDescriptors'); 78 | mergeAssign(dstDescriptor, srcDescriptor, 'staticProperties'); 79 | deepMergeAssign(dstDescriptor, srcDescriptor, 'staticDeepProperties'); 80 | mergeAssign(dstDescriptor, srcDescriptor, 'staticPropertyDescriptors'); 81 | mergeAssign(dstDescriptor, srcDescriptor, 'configuration'); 82 | deepMergeAssign(dstDescriptor, srcDescriptor, 'deepConfiguration'); 83 | concatAssignFunctions(dstDescriptor, srcDescriptor.initializers, 'initializers'); 84 | concatAssignFunctions(dstDescriptor, srcDescriptor.composers, 'composers'); 85 | }; 86 | /** 87 | * TODO 88 | */ 89 | const compose = function compose(...args) { 90 | const descriptor = {}; 91 | const composables = []; 92 | if ((0, is_1.isComposable)(this)) { 93 | mergeComposable(descriptor, this); 94 | composables.push(this); 95 | } 96 | for (const arg of args) { 97 | if ((0, is_1.isComposable)(arg)) { 98 | mergeComposable(descriptor, arg); 99 | composables.push(arg); 100 | } 101 | } 102 | let stamp = createStamp(descriptor, compose); 103 | const { composers } = descriptor; 104 | if ((0, is_1.isArray)(composers) && composers.length > 0) { 105 | for (const composer of composers) { 106 | const returnedValue = composer({ stamp, composables }); 107 | if ((0, is_1.isStamp)(returnedValue)) 108 | stamp = returnedValue; 109 | } 110 | } 111 | return stamp; 112 | }; 113 | exports.default = compose; 114 | // For CommonJS default export support 115 | module.exports = compose; 116 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: compose }); 117 | -------------------------------------------------------------------------------- /packages/compose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/compose", 3 | "version": "1.0.2", 4 | "license": "MIT", 5 | "description": "Compose function implementation to create stamps", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/compose" 9 | }, 10 | "keywords": [ 11 | "compose", 12 | "object", 13 | "prototype", 14 | "object oriented", 15 | "browser", 16 | "inheritance", 17 | "oop", 18 | "node", 19 | "factory", 20 | "class", 21 | "stamp" 22 | ], 23 | "scripts": { 24 | "build": "tsc", 25 | "link": "npm run build && lerna link" 26 | }, 27 | "dependencies": { 28 | "@stamp/core": "^1.0.1", 29 | "@stamp/is": "^1.0.0" 30 | }, 31 | "devDependencies": { 32 | "@stamp/check-compose": "^1.0.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/compose/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/configure/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/configure/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/configure 2 | 3 | _Access configuration of your stamps anywhere_ 4 | 5 | Configuration is powerful feature of stamps as it allows you to store additional information with the stamp without interfering with properties or methods. Consider following example. 6 | 7 | ## Usage 8 | 9 | ```js 10 | import compose from '@stamp/compose' 11 | import jwt from 'jsonwebtoken' 12 | 13 | const Jwt = compose({ 14 | configuration: { 15 | jwtSecret: process.env.SECRET, 16 | }, 17 | initializers: [ 18 | initializeJwt(_, { stamp }) { 19 | const { jwtSecret } = stamp.compose.configuration 20 | ... 21 | this.createJwtToken = (payload) => jwt.sign(payload, jwtSecret) 22 | this.verifyJwtToken = (token) => jwt.verify(token, jwtSecret) 23 | } 24 | ] 25 | }) 26 | ``` 27 | 28 | That approach brings several advantages. 29 | 30 | * Clearly specify what makes the stamp tick. 31 | * Configured values are immutable. 32 | * Stamp with a modified configuration can be made. 33 | 34 | The last bullet is especially useful for automated testing allowing you to insert different values based on various conditions. Unfortunately, there is apparent boilerplate hidden behind this, and it can get tedious for a larger project. 35 | 36 | Now consider next example that is using `@stamp/configure` stamp. 37 | 38 | ```js 39 | import Configure from '@stamp/configure' 40 | import jwt from 'jsonwebtoken' 41 | 42 | const Jwt = Configure.compose({ 43 | configuration: { 44 | jwtSecret: process.env.SECRET, 45 | }, 46 | methods: { 47 | createJwtToken(payload) { 48 | return jwt.sign(payload, this.config.jwtSecret) 49 | }, 50 | verifyJwtToken(token) { 51 | return jwt.verify(token, this.config.jwtSecret) 52 | } 53 | } 54 | }) 55 | ``` 56 | 57 | Looks good, doesn't it? But wait, all those advantages of the configuration are suddenly gone, right? Not exactly. 58 | 59 | Under the hood, we are using `@stamp/privatize` stamp. That allows us to access `this.config` within our methods and yet keep them hidden from outside world. Immutability is ensured by using `Object.freeze()`. 60 | 61 | The `deepConfiguration` gets assigned to the same `this.config` object as well while the `configuration` has a precedence over it. In case of name conflict, the value from `configuration` always wins. 62 | 63 | ### No fan of @stamp/privatize ? 64 | 65 | By including `@stamp/configure` your whole stamp is privatized by default which you may not like that much. For that case, we are offering opt-out option of using `Configure.noPrivatize()` instead of plain `Configure`. 66 | -------------------------------------------------------------------------------- /packages/configure/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const privatize_1 = __importDefault(require("@stamp/privatize")); 8 | const configure = function configure(_, options) { 9 | const { configuration } = options.stamp.compose; 10 | const { deepConfiguration } = options.stamp.compose; 11 | this.config = Object.freeze(Object.assign(Object.assign({}, deepConfiguration), configuration)); 12 | }; 13 | const ConfigurePublic = (0, compose_1.default)({ 14 | initializers: [configure], 15 | }); 16 | /** 17 | * TODO 18 | */ 19 | const ConfigurePrivate = ConfigurePublic.compose(privatize_1.default); 20 | ConfigurePrivate.noPrivatize = () => ConfigurePublic; 21 | exports.default = ConfigurePrivate; 22 | // For CommonJS default export support 23 | module.exports = ConfigurePrivate; 24 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: ConfigurePrivate }); 25 | -------------------------------------------------------------------------------- /packages/configure/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { Stamp, Initializer, PropertyMap } from '@stamp/compose'; 2 | import Privatize from '@stamp/privatize'; 3 | 4 | interface HasConfig { 5 | config: PropertyMap; 6 | } 7 | const configure: Initializer = function configure(_, options) { 8 | const { configuration } = options.stamp.compose; 9 | const { deepConfiguration } = options.stamp.compose; 10 | (this as HasConfig).config = Object.freeze({ ...deepConfiguration, ...configuration }); 11 | }; 12 | 13 | interface ConfigureStamp extends Stamp { 14 | noPrivatize: () => ConfigureStamp; 15 | } 16 | const ConfigurePublic: ConfigureStamp = compose({ 17 | initializers: [configure], 18 | }) as ConfigureStamp; 19 | 20 | /** 21 | * TODO 22 | */ 23 | const ConfigurePrivate = ConfigurePublic.compose(Privatize) as ConfigureStamp; 24 | 25 | ConfigurePrivate.noPrivatize = (): ConfigureStamp => ConfigurePublic; 26 | 27 | export default ConfigurePrivate; 28 | 29 | // For CommonJS default export support 30 | module.exports = ConfigurePrivate; 31 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: ConfigurePrivate }); 32 | -------------------------------------------------------------------------------- /packages/configure/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/configure", 3 | "version": "1.0.2", 4 | "license": "MIT", 5 | "description": "Access configuration of your stamps anywhere", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/configure" 9 | }, 10 | "keywords": [ 11 | "compose", 12 | "configure" 13 | ], 14 | "scripts": { 15 | "build": "tsc", 16 | "link": "npm run build && lerna link" 17 | }, 18 | "dependencies": { 19 | "@stamp/compose": "^1.0.2", 20 | "@stamp/privatize": "^1.0.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/configure/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/convert-class/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /packages/convert-class/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/convert-class/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/convert-class 2 | 3 | Converts an ES6 class to a stamp respecting the class's inheritance chain. The prototype chain is squashed into the `methods` of a stamp. 4 | 5 | `npm i -S @stamp/convert-class` 6 | 7 | # Example 8 | 9 | ```js 10 | class CParent { 11 | constructor(arg1) { 12 | this.parentProp = arg1 13 | } 14 | 15 | commonMethod() { return 'parent' } 16 | parentMethod() { return 'parent' } 17 | 18 | static commonStaticMethod() { return 'parent' } 19 | static parentStaticMethod() { return 'parent' } 20 | } 21 | 22 | class CChild extends CParent { 23 | constructor(arg1, arg2) { 24 | super(arg1) 25 | this.childProp = arg2 26 | } 27 | 28 | commonMethod() { return super.commonMethod() } // overriding parent method 29 | childMethod() { return 'child' } 30 | 31 | static commonStaticMethod() { return super.commonStaticMethod() } // overriding parent static method 32 | static childStaticMethod() { return 'child' } 33 | } 34 | 35 | const convertClass = require('@stamp/convert-class'); 36 | 37 | const Stamp = convertClass(CChild); 38 | 39 | // parent class method 40 | Stamp.compose.methods.parentMethod === CParent.prototype.parentMethod; 41 | // child class method 42 | Stamp.compose.methods.childMethod === CChild.prototype.childMethod; 43 | // the overridden method 44 | Stamp.compose.methods.commonMethod === CChild.prototype.commonMethod; 45 | // The `super` inside regular method is delegated to parent 46 | Stamp().commonMethod() === (new CChild()).commonMethod() === 'parent'; 47 | 48 | // parent static method 49 | Stamp.parentStaticMethod === CParent.parentStaticMethod; 50 | // child static method 51 | Stamp.childStaticMethod === CChild.childStaticMethod; 52 | // the overridden static method 53 | Stamp.commonStaticMethod === CChild.commonStaticMethod; 54 | // The `super` inside static methods of child classes is also delegated to parent 55 | Stamp.commonStaticMethod() === CChild.commonStaticMethod() === 'parent'; 56 | ``` 57 | -------------------------------------------------------------------------------- /packages/convert-class/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const { prototype: functionPrototype } = Function; 8 | const { assign } = Object; 9 | const { construct, get, getPrototypeOf, ownKeys, set } = Reflect; 10 | const isClass = (value) => typeof value === 'function' && /^\s*class\s/.test(value.toString()); 11 | const isFunction = (value) => value === functionPrototype; 12 | const copyPropertiesFrom = (srcObj) => (destObj, key) => { 13 | if (key !== 'length' && key !== 'name' && key !== 'prototype') 14 | set(destObj, key, get(srcObj, key)); 15 | return destObj; 16 | }; 17 | const classStaticProperties = (ctor) => isFunction(ctor) ? {} : ownKeys(ctor).reduce(copyPropertiesFrom(ctor), classStaticProperties(getPrototypeOf(ctor) || {})); 18 | const copyMethodsFrom = (srcObj) => (destObj, key) => { 19 | if (key !== 'constructor') 20 | set(destObj, key, get(srcObj, key)); 21 | return destObj; 22 | }; 23 | const classMethods = (ctor) => (isFunction(ctor) 24 | ? {} 25 | : ownKeys(ctor.prototype).reduce(copyMethodsFrom(ctor.prototype), classMethods(getPrototypeOf(ctor)))); 26 | const init = (ctor) => 27 | // eslint-disable-next-line func-names,,@typescript-eslint/no-unused-vars 28 | function (_, { instance, args }) { 29 | if (this) 30 | assign(this, construct(ctor, args)); 31 | }; 32 | /** 33 | * TODO 34 | */ 35 | const convertClass = (ctor) => isClass(ctor) 36 | ? (0, compose_1.default)({ 37 | initializers: [init(ctor)], 38 | methods: classMethods(ctor), 39 | staticProperties: classStaticProperties(ctor), 40 | staticPropertyDescriptors: { name: { value: ctor.name } }, 41 | }) 42 | : (0, compose_1.default)(); 43 | exports.default = convertClass; 44 | // For CommonJS default export support 45 | module.exports = convertClass; 46 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: convertClass }); 47 | -------------------------------------------------------------------------------- /packages/convert-class/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { Initializer, PropertyMap, Stamp } from '@stamp/compose'; 2 | 3 | const { prototype: functionPrototype } = Function; 4 | const { assign } = Object; 5 | const { construct, get, getPrototypeOf, ownKeys, set } = Reflect; 6 | 7 | const isClass = (value: unknown): unknown => typeof value === 'function' && /^\s*class\s/.test(value.toString()); 8 | 9 | const isFunction = (value: unknown): value is Function => value === functionPrototype; 10 | 11 | const copyPropertiesFrom = (srcObj: object) => (destObj: PropertyMap, key: PropertyKey): PropertyMap => { 12 | if (key !== 'length' && key !== 'name' && key !== 'prototype') set(destObj, key, get(srcObj, key)); 13 | return destObj; 14 | }; 15 | 16 | const classStaticProperties = (ctor: object): PropertyMap => 17 | isFunction(ctor) ? {} : ownKeys(ctor).reduce(copyPropertiesFrom(ctor), classStaticProperties(getPrototypeOf(ctor) || {})); 18 | 19 | interface ObjectWithPrototype extends PropertyMap { 20 | prototype: object; 21 | } 22 | const copyMethodsFrom = (srcObj: object) => (destObj: object, key: PropertyKey): object => { 23 | if (key !== 'constructor') set(destObj, key, get(srcObj, key)); 24 | return destObj; 25 | }; 26 | 27 | const classMethods = (ctor: ObjectConstructor | ObjectWithPrototype): ObjectWithPrototype => 28 | (isFunction(ctor) 29 | ? {} 30 | : ownKeys(ctor.prototype).reduce( 31 | copyMethodsFrom(ctor.prototype), 32 | classMethods(getPrototypeOf(ctor) as ObjectWithPrototype) 33 | )) as ObjectWithPrototype; 34 | 35 | const init = (ctor: ObjectConstructor): Initializer => 36 | // eslint-disable-next-line func-names,,@typescript-eslint/no-unused-vars 37 | function (_, { instance, args }) { 38 | if (this) assign(this, construct(ctor, args)); 39 | } as Initializer; 40 | 41 | /** 42 | * TODO 43 | */ 44 | const convertClass = (ctor: ObjectConstructor): Stamp => 45 | isClass(ctor) 46 | ? compose({ 47 | initializers: [init(ctor)], 48 | methods: classMethods(ctor), 49 | staticProperties: classStaticProperties(ctor), 50 | staticPropertyDescriptors: { name: { value: ctor.name } }, 51 | }) 52 | : compose(); 53 | 54 | export default convertClass; 55 | 56 | // For CommonJS default export support 57 | module.exports = convertClass; 58 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: convertClass }); 59 | -------------------------------------------------------------------------------- /packages/convert-class/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/convert-class", 3 | "version": "1.0.3", 4 | "description": "Converts ES6 class to stamp", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/convert-class" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/convert-class/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/core 2 | 3 | _Core functions for creating stamps_ 4 | 5 | ```js 6 | const {assign, merge} = require('@stamp/core'); 7 | // or 8 | import {assign, merge} from '@stamp/core'; 9 | ``` 10 | 11 | ### assign(dst, ...sources) 12 | _`@stamp/core/assign`_ 13 | 14 | Mutates destination object with shallow assign of passed source objects. Returns destination object. 15 | 16 | ### merge(dst, ...sources) 17 | _`@stamp/core/merge`_ 18 | 19 | * Mutates destination object by deeply merging passed source objects. 20 | * Arrays are concatenated, not overwritten. 21 | * Everything else but plain objects are copied by reference. 22 | 23 | Returns destination object/array or a new object/array in case it was not. 24 | -------------------------------------------------------------------------------- /packages/core/assign.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const { defineProperty, getOwnPropertyDescriptor, ownKeys } = Reflect; 4 | const assignOne = (dst, src) => { 5 | if (src != null) { 6 | // We need to copy regular properties, symbols, getters and setters. 7 | const keys = ownKeys(src); 8 | for (const key of keys) { 9 | defineProperty(dst, key, getOwnPropertyDescriptor(src, key)); 10 | } 11 | } 12 | return dst; 13 | }; 14 | /** 15 | * Mutates destination object with shallow assign of passed source objects. Returns destination object. 16 | */ 17 | const assign = (dst, ...args) => { 18 | for (const arg of args) { 19 | // eslint-disable-next-line no-param-reassign 20 | dst = assignOne(dst, arg); 21 | } 22 | return dst; 23 | }; 24 | exports.default = assign; 25 | // For CommonJS default export support 26 | module.exports = assign; 27 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: assign }); 28 | -------------------------------------------------------------------------------- /packages/core/assign.ts: -------------------------------------------------------------------------------- 1 | const { defineProperty, getOwnPropertyDescriptor, ownKeys } = Reflect; 2 | 3 | const assignOne = (dst: object, src: object | undefined): object => { 4 | if (src != null) { 5 | // We need to copy regular properties, symbols, getters and setters. 6 | const keys = ownKeys(src); 7 | for (const key of keys) { 8 | defineProperty(dst, key, getOwnPropertyDescriptor(src, key) as PropertyDescriptor); 9 | } 10 | } 11 | 12 | return dst; 13 | }; 14 | 15 | /** 16 | * Mutates destination object with shallow assign of passed source objects. Returns destination object. 17 | */ 18 | const assign = (dst: T, ...args: (object | undefined)[]): T => { 19 | for (const arg of args) { 20 | // eslint-disable-next-line no-param-reassign 21 | dst = assignOne(dst, arg) as T; 22 | } 23 | return dst; 24 | }; 25 | 26 | export default assign; 27 | 28 | // For CommonJS default export support 29 | module.exports = assign; 30 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: assign }); 31 | -------------------------------------------------------------------------------- /packages/core/get-own-property-keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const { ownKeys } = Reflect; 4 | /** @deprecated Use Reflect.ownKeys() instead */ 5 | const getOwnPropertyKeys = ownKeys; 6 | exports.default = getOwnPropertyKeys; 7 | // For CommonJS default export support 8 | module.exports = getOwnPropertyKeys; 9 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: getOwnPropertyKeys }); 10 | -------------------------------------------------------------------------------- /packages/core/get-own-property-keys.ts: -------------------------------------------------------------------------------- 1 | const { ownKeys } = Reflect; 2 | 3 | /** @deprecated Use Reflect.ownKeys() instead */ 4 | const getOwnPropertyKeys = ownKeys; 5 | 6 | export default getOwnPropertyKeys; 7 | 8 | // For CommonJS default export support 9 | module.exports = getOwnPropertyKeys; 10 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: getOwnPropertyKeys }); 11 | -------------------------------------------------------------------------------- /packages/core/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.getOwnPropertyKeys = exports.merge = exports.assign = void 0; 7 | var assign_1 = require("./assign"); 8 | Object.defineProperty(exports, "assign", { enumerable: true, get: function () { return __importDefault(assign_1).default; } }); 9 | var merge_1 = require("./merge"); 10 | Object.defineProperty(exports, "merge", { enumerable: true, get: function () { return __importDefault(merge_1).default; } }); 11 | /** @deprecated Use Reflect.ownKeys() instead */ 12 | var get_own_property_keys_1 = require("./get-own-property-keys"); 13 | Object.defineProperty(exports, "getOwnPropertyKeys", { enumerable: true, get: function () { return __importDefault(get_own_property_keys_1).default; } }); 14 | -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | export { default as assign } from './assign'; 2 | export { default as merge } from './merge'; 3 | 4 | /** @deprecated Use Reflect.ownKeys() instead */ 5 | export { default as getOwnPropertyKeys } from './get-own-property-keys'; 6 | -------------------------------------------------------------------------------- /packages/core/merge.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const is_1 = require("@stamp/is"); 4 | const { defineProperty, get, getOwnPropertyDescriptor, ownKeys, set } = Reflect; 5 | /** 6 | * The 'src' argument plays the command role. 7 | * The returned values is always of the same type as the 'src'. 8 | * @param dst The object to merge into 9 | * @param src The object to merge from 10 | * @returns {*} 11 | */ 12 | function mergeOne(dst, src) { 13 | if (src !== undefined) { 14 | // According to specification arrays must be concatenated. 15 | // Also, the '.concat' creates a new array instance. Overrides the 'dst'. 16 | if ((0, is_1.isArray)(src)) 17 | return (0, is_1.isArray)(dst) ? [...dst, ...src] : [...src]; 18 | // Now deal with non plain 'src' object. 'src' overrides 'dst' 19 | // Note that functions are also assigned! We do not deep merge functions. 20 | if ((0, is_1.isPlainObject)(src)) { 21 | const keys = ownKeys(src); 22 | for (const key of keys) { 23 | const desc = getOwnPropertyDescriptor(src, key); 24 | // is this a regular property? 25 | if (getOwnPropertyDescriptor(desc, 'value') !== undefined) { 26 | // Do not merge properties with the 'undefined' value. 27 | if (desc.value !== undefined) { 28 | const dstValue = get(dst, key); 29 | const srcValue = get(src, key); 30 | // Recursive calls to mergeOne() must allow only plain objects or arrays in dst 31 | const newDst = (0, is_1.isPlainObject)(dstValue) || (0, is_1.isArray)(srcValue) ? dstValue : {}; 32 | // deep merge each property. Recursion! 33 | set(dst, key, mergeOne(newDst, srcValue)); 34 | } 35 | } 36 | else { 37 | // nope, it looks like a getter/setter 38 | defineProperty(dst, key, desc); 39 | } 40 | } 41 | } 42 | else { 43 | return src; 44 | } 45 | } 46 | return dst; 47 | } 48 | /** 49 | * Mutates destination object by deeply merging passed source objects. 50 | * Arrays are concatenated, not overwritten. 51 | * Everything else but plain objects are copied by reference. 52 | * 53 | * Returns destination object/array or a new object/array in case it was not. 54 | */ 55 | const merge = (dst, ...args) => { 56 | for (const arg of args) { 57 | // eslint-disable-next-line no-param-reassign 58 | dst = mergeOne(dst, arg); 59 | } 60 | return dst; 61 | }; 62 | exports.default = merge; 63 | // For CommonJS default export support 64 | module.exports = merge; 65 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: merge }); 66 | -------------------------------------------------------------------------------- /packages/core/merge.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isPlainObject } from '@stamp/is'; 2 | 3 | const { defineProperty, get, getOwnPropertyDescriptor, ownKeys, set } = Reflect; 4 | 5 | /** 6 | * The 'src' argument plays the command role. 7 | * The returned values is always of the same type as the 'src'. 8 | * @param dst The object to merge into 9 | * @param src The object to merge from 10 | * @returns {*} 11 | */ 12 | function mergeOne(dst: object, src: unknown): unknown { 13 | if (src !== undefined) { 14 | // According to specification arrays must be concatenated. 15 | // Also, the '.concat' creates a new array instance. Overrides the 'dst'. 16 | if (isArray(src)) return isArray(dst) ? [...dst, ...src] : [...src]; 17 | 18 | // Now deal with non plain 'src' object. 'src' overrides 'dst' 19 | // Note that functions are also assigned! We do not deep merge functions. 20 | if (isPlainObject(src)) { 21 | const keys = ownKeys(src); 22 | for (const key of keys) { 23 | const desc = getOwnPropertyDescriptor(src, key) as PropertyDescriptor; 24 | // is this a regular property? 25 | if (getOwnPropertyDescriptor(desc, 'value') !== undefined) { 26 | // Do not merge properties with the 'undefined' value. 27 | if (desc.value !== undefined) { 28 | const dstValue = get(dst, key); 29 | const srcValue = get(src, key); 30 | // Recursive calls to mergeOne() must allow only plain objects or arrays in dst 31 | const newDst = isPlainObject(dstValue) || isArray(srcValue) ? dstValue : {}; 32 | // deep merge each property. Recursion! 33 | set(dst, key, mergeOne(newDst, srcValue)); 34 | } 35 | } else { 36 | // nope, it looks like a getter/setter 37 | defineProperty(dst, key, desc); 38 | } 39 | } 40 | } else { 41 | return src; 42 | } 43 | } 44 | 45 | return dst; 46 | } 47 | 48 | /** 49 | * Mutates destination object by deeply merging passed source objects. 50 | * Arrays are concatenated, not overwritten. 51 | * Everything else but plain objects are copied by reference. 52 | * 53 | * Returns destination object/array or a new object/array in case it was not. 54 | */ 55 | const merge = (dst: T, ...args: (object | undefined)[]): T => { 56 | for (const arg of args) { 57 | // eslint-disable-next-line no-param-reassign 58 | dst = mergeOne(dst, arg) as T; 59 | } 60 | return dst; 61 | }; 62 | 63 | export default merge; 64 | 65 | // For CommonJS default export support 66 | module.exports = merge; 67 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: merge }); 68 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/core", 3 | "version": "1.0.1", 4 | "license": "MIT", 5 | "description": "Core functions for creating stamps", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/core" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "link": "npm run build && lerna link" 13 | }, 14 | "dependencies": { 15 | "@stamp/is": "^1.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/eventemittable/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/eventemittable/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/eventemittable 2 | 3 | _Node.js' EventEmitter as a stamp_ 4 | 5 | ```js 6 | const EventEmittable = require('@stamp/eventemittable'); 7 | // or 8 | import EventEmittable from '@stamp/eventemittable'; 9 | ``` 10 | 11 | ### Example 12 | 13 | Create event emitter object: 14 | ```js 15 | const emitter = EventEmittable(); 16 | ``` 17 | 18 | Create a Stamp which implements Node.js' `EventEmitter` API via composition: 19 | ```js 20 | import stampit from '@stamp/it'; 21 | 22 | const MyStamp = stampit({ 23 | methods: { 24 | foo () { 25 | this.emit('foo', 'bar'); 26 | } 27 | } 28 | }) 29 | .compose(EventEmittable); 30 | 31 | const myObject = MyStamp(); 32 | myObject.on('foo', value => { 33 | console.log(`value: ${value}`); 34 | }); 35 | myObject.foo(); // prints "value: bar" 36 | ``` 37 | 38 | ### Notes 39 | 40 | - For portability, this package consumes the userland `EventEmitter` implementation of the [events](https://npm.im/events) package. 41 | - `domain`s are not supported. 42 | -------------------------------------------------------------------------------- /packages/eventemittable/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const events_1 = require("events"); 8 | const listenerCount = (emitter, event) => emitter.listenerCount(event); 9 | /** 10 | * TODO 11 | */ 12 | const EventEmittable = (0, compose_1.default)({ 13 | staticProperties: { 14 | defaultMaxListeners: events_1.EventEmitter.defaultMaxListeners, 15 | listenerCount, 16 | }, 17 | methods: events_1.EventEmitter.prototype, 18 | }); 19 | exports.default = EventEmittable; 20 | // For CommonJS default export support 21 | module.exports = EventEmittable; 22 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: EventEmittable }); 23 | -------------------------------------------------------------------------------- /packages/eventemittable/index.ts: -------------------------------------------------------------------------------- 1 | import compose from '@stamp/compose'; 2 | import { EventEmitter } from 'events'; 3 | 4 | interface ListenerCount { 5 | (emitter: EventEmitter, event: string): number; 6 | } 7 | 8 | const listenerCount: ListenerCount = (emitter, event) => emitter.listenerCount(event); 9 | 10 | /** 11 | * TODO 12 | */ 13 | const EventEmittable = compose({ 14 | staticProperties: { 15 | defaultMaxListeners: EventEmitter.defaultMaxListeners, 16 | listenerCount, 17 | }, 18 | methods: EventEmitter.prototype, 19 | }); 20 | 21 | export default EventEmittable; 22 | 23 | // For CommonJS default export support 24 | module.exports = EventEmittable; 25 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: EventEmittable }); 26 | -------------------------------------------------------------------------------- /packages/eventemittable/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/eventemittable", 3 | "version": "1.0.2", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@stamp/eventemittable", 9 | "version": "1.0.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "events": "^3.0.0" 13 | } 14 | }, 15 | "node_modules/events": { 16 | "version": "3.0.0", 17 | "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", 18 | "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", 19 | "engines": { 20 | "node": ">=0.8.x" 21 | } 22 | } 23 | }, 24 | "dependencies": { 25 | "events": { 26 | "version": "3.0.0", 27 | "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", 28 | "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/eventemittable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/eventemittable", 3 | "version": "1.0.2", 4 | "license": "MIT", 5 | "description": "Node.js' EventEmitter as a stamp", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/eventemittable" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "link": "npm run build && lerna link" 13 | }, 14 | "dependencies": { 15 | "@stamp/compose": "^1.0.2", 16 | "events": "^3.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/eventemittable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/fp-constructor/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/fp-constructor/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/fp-constructor 2 | 3 | * _Adds the `Stamp.of` static property referencing Stamp itself_ 4 | * _Adds the `instance.constructor` property referencing Stamp itself_ 5 | 6 | By composing the `@stamp/fp-constructor` into your stamp 7 | ```js 8 | MyStamp = MyStamp.compose(FpConstructor); 9 | ``` 10 | you get the static method `.of`: 11 | ```js 12 | MyStamp.of === MyStamp; // true 13 | ``` 14 | 15 | and the instance method, `.constructor`: 16 | ```js 17 | instance = MyStamp(/* ...options */); 18 | instance.constructor === MyStamp; // true 19 | ``` 20 | 21 | ## Motivation 22 | 23 | Frequently, it's desirable to instantiate a new instance of a given datatype inside a generic function. However, JavaScript lacks a built-in way to do that which is reliable. Classes throw errors if you leave off `new`, and arrow function factories will throw if you try to instantiate an instance with `new`, and there's no standard way to inject a value into the new datatype. 24 | 25 | The `Stamp.of()` is a standard way to create a new value of a given stamp where the calling conventions are unambiguous: It does not require `new`, and you inject the value by passing the necessary arguments directly into the `.of()` static method. 26 | 27 | Having the `.of()` method only solves half of the problem, though. You still need a way got get a handle on the stamp from an object instance. You can do that with the `instance.constructor()` method. 28 | 29 | Here's an example of what you can do with the combination. The following `empty()` utility will return an empty instance of whatever supporting datatype you pass in, including standard JavaScript arrays: 30 | 31 | ```js 32 | const empty = ({ constructor } = {}) => constructor.of ? 33 | constructor.of() : 34 | undefined 35 | ; 36 | 37 | const foo = [23]; 38 | 39 | console.log( 40 | empty(foo) // [] 41 | ); 42 | ``` 43 | 44 | All [applicative functors](https://github.com/fantasyland/fantasy-land#applicative) in JavaScript should implement `.of()`. 45 | 46 | ## Usage 47 | 48 | ```js 49 | import FpConstructor from '@stamp/fp-constructor'; 50 | 51 | MyStamp = MyStamp.compose(FpConstructor); 52 | ``` 53 | 54 | ## API 55 | 56 | ### Static methods 57 | 58 | #### methods 59 | `stamp.of(...args) => Object` 60 | 61 | ### Instance methods 62 | 63 | ### methods 64 | `instance.constructor(...args) => Object` 65 | -------------------------------------------------------------------------------- /packages/fp-constructor/__tests__/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const compose = require('@stamp/compose'); 4 | const FpConstructor = require('..'); 5 | 6 | describe('@stamp/fp-constructor Stamp.of', function() { 7 | it('should add .of static method referring to the stamp', function() { 8 | const Stamp = compose(FpConstructor); 9 | 10 | expect(Stamp.of).toBe(Stamp); 11 | }); 12 | 13 | it('should refer to the correct stamp regardless of other stamps in the composition', function() { 14 | const Stamp = compose({ staticProperties: { of: 1 } }) 15 | .compose({ staticProperties: { of: 2 } }, FpConstructor, { staticProperties: { of: 3 } }) 16 | .compose({ staticProperties: { of: 4 } }); 17 | 18 | expect(Stamp.of).toBe(Stamp); 19 | }); 20 | }); 21 | 22 | describe('@stamp/fp-constructor instance.constructor', function() { 23 | it('should add .constructor method to the stamp', function() { 24 | const Stamp = compose(FpConstructor); 25 | const instance = Stamp(); 26 | 27 | expect(instance.constructor).toBe(Stamp); 28 | }); 29 | 30 | it('should not be an own prop on the instance', function() { 31 | const Stamp = compose(FpConstructor); 32 | const instance = Stamp(); 33 | 34 | expect(Object.getPrototypeOf(instance).constructor).toBe(Stamp); 35 | }); 36 | 37 | it('should refer to the correct stamp regardless of other stamps in the composition', function() { 38 | const Stamp = compose({ methods: { constructor: 1 } }) 39 | .compose({ methods: { constructor: 2 } }, FpConstructor, { methods: { constructor: 3 } }) 40 | .compose({ methods: { constructor: 4 } }); 41 | const instance = Stamp(); 42 | 43 | expect(instance.constructor).toBe(Stamp); 44 | }); 45 | 46 | it('should not break other methods', function() { 47 | const foo = function foo() {}; 48 | const bar = function bar() {}; 49 | const Stamp = compose({ methods: { foo } }) 50 | .compose(FpConstructor) 51 | .compose({ methods: { bar } }); 52 | const instance = Stamp(); 53 | 54 | expect(instance.foo).toBe(foo); 55 | expect(instance.bar).toBe(bar); 56 | }); 57 | }); 58 | 59 | describe('@stamp/fp-constructor Stamp.constructor (deprecated)', function() { 60 | it('should add .constructor static method referring to the stamp', function() { 61 | const Stamp = compose(FpConstructor); 62 | 63 | expect(Stamp.constructor).toBe(Stamp); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/fp-constructor/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const { get } = Reflect; 8 | /** 9 | * TODO 10 | */ 11 | const FpConstructor = (0, compose_1.default)({ 12 | composers: [ 13 | ((opts) => { 14 | var _a; 15 | const optsStamp = get(opts, 'stamp'); 16 | optsStamp.of = optsStamp; 17 | optsStamp.constructor = optsStamp; 18 | optsStamp.compose.methods = (_a = optsStamp.compose.methods) !== null && _a !== void 0 ? _a : {}; 19 | optsStamp.compose.methods.constructor = optsStamp; 20 | }), 21 | ], 22 | }); 23 | exports.default = FpConstructor; 24 | // For CommonJS default export support 25 | module.exports = FpConstructor; 26 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: FpConstructor }); 27 | -------------------------------------------------------------------------------- /packages/fp-constructor/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { Composer, Stamp } from '@stamp/compose'; 2 | 3 | const { get } = Reflect; 4 | 5 | /** 6 | * TODO 7 | */ 8 | const FpConstructor = compose({ 9 | composers: [ 10 | ((opts) => { 11 | const optsStamp = get(opts, 'stamp') as (Stamp & { of: Stamp }); 12 | optsStamp.of = optsStamp; 13 | optsStamp.constructor = optsStamp; 14 | optsStamp.compose.methods = optsStamp.compose.methods ?? {}; 15 | optsStamp.compose.methods.constructor = optsStamp; 16 | }) as Composer, 17 | ], 18 | }); 19 | 20 | export default FpConstructor; 21 | 22 | // For CommonJS default export support 23 | module.exports = FpConstructor; 24 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: FpConstructor }); 25 | -------------------------------------------------------------------------------- /packages/fp-constructor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/fp-constructor", 3 | "version": "1.0.1", 4 | "description": "Adds the Functional Programming capabilities to your stamps", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/fp-constructor" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/fp-constructor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/init-property/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/init-property/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/init-property 2 | 3 | _Replaces properties which reference stamps with objects created from the stamps_ 4 | 5 | If your StampA would have, say, property `foo` which references a StampB, then the property will be replaced with the StampB instance. The parameter for the `StampB(arg)` will be taken form the `foo` property of the StampA argument. 6 | 7 | You might find it useful for better Dependency Injection pattern. 8 | 9 | ## Usage 10 | 11 | ```js 12 | const StampA = compose({ 13 | properties: { 14 | foo: StampB 15 | } 16 | }) 17 | .compose(InitProperty); 18 | 19 | // StampB will receive initializer argument `{bar: 'baz'}` 20 | StampA({foo: {bar: 'baz'}}); 21 | ``` 22 | 23 | 24 | ## Example 25 | 26 | ```js 27 | import InitProperty from '@stamp/init-property'; 28 | 29 | import Oauth2 from './stamps/oauth2'; 30 | 31 | const GravatarClient = compose(InitProperty, { 32 | properties: { 33 | auth: Oauth2 // the 'auth' property is expected to be passed to the stamp 34 | }, 35 | methods: { 36 | getProfilePicture() { 37 | return fetch('gravatar.com', { 38 | headers: { 39 | Authorization: this.auth.getHttpHeader() // accessing this.auth 40 | } 41 | }); 42 | } 43 | } 44 | }); 45 | 46 | const gravatarClient = GravatarClient({ 47 | auth: { // Passing the data for the 'Oauth2' stamp 48 | client_id: '1', client_secret: '2', grant_type: 'simple' 49 | } 50 | }); 51 | gravatarClient.getProfilePicture().then(stream => fs.writeFileSync(stream)); 52 | ``` 53 | -------------------------------------------------------------------------------- /packages/init-property/__tests__/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const compose = require('@stamp/compose'); 4 | const InitProperty = require('..'); 5 | 6 | describe('@stamp/init-property', function() { 7 | it('creates sub instance from stamps assigned to properties', function() { 8 | const SubStamp1 = compose({ 9 | properties: { subStamp1: true }, 10 | }); 11 | const SubStamp2 = compose({ 12 | properties: { subStamp2: true }, 13 | }); 14 | const MainStamp = InitProperty.compose({ 15 | properties: { 16 | prop1: SubStamp1, 17 | prop2: SubStamp2, 18 | }, 19 | }); 20 | 21 | const opts = { prop1: {}, prop2: {} }; 22 | const instance = MainStamp(opts); 23 | 24 | expect(instance.prop1).toStrictEqual({ subStamp1: true }); 25 | expect(instance.prop2).toStrictEqual({ subStamp2: true }); 26 | }); 27 | 28 | it('should ignore non stamp properties', function() { 29 | const MainStamp = InitProperty.compose({ 30 | properties: { 31 | prop1: 'whatever', 32 | prop2: 42, 33 | }, 34 | }); 35 | 36 | const opts = { prop1: {}, prop2: {} }; 37 | const instance = MainStamp(opts); 38 | 39 | expect(instance.prop1).toBe('whatever'); 40 | expect(instance.prop2).toBe(42); 41 | }); 42 | 43 | it('passes same named value from opts', function() { 44 | let opts1; 45 | let opts2; 46 | const SubStamp1 = compose({ 47 | initializers: [ 48 | function(opts) { 49 | opts1 = opts; 50 | }, 51 | ], 52 | }); 53 | const SubStamp2 = compose({ 54 | initializers: [ 55 | function(opts) { 56 | opts2 = opts; 57 | }, 58 | ], 59 | }); 60 | const MainStamp = InitProperty.compose({ 61 | properties: { 62 | prop1: SubStamp1, 63 | prop2: SubStamp2, 64 | }, 65 | }); 66 | 67 | const opts = { prop1: {}, prop2: {} }; 68 | MainStamp(opts); 69 | 70 | expect(opts1).toBe(opts.prop1); 71 | expect(opts2).toBe(opts.prop2); 72 | }); 73 | 74 | it('passes the rest of the factory arguments', function() { 75 | let args1; 76 | let args2; 77 | const SubStamp1 = compose({ 78 | initializers: [ 79 | function(_, ref) { 80 | args1 = ref.args; 81 | }, 82 | ], 83 | }); 84 | const SubStamp2 = compose({ 85 | initializers: [ 86 | function(_, ref) { 87 | args2 = ref.args; 88 | }, 89 | ], 90 | }); 91 | const MainStamp = InitProperty.compose({ 92 | properties: { 93 | prop1: SubStamp1, 94 | prop2: SubStamp2, 95 | }, 96 | }); 97 | 98 | const argA = { A: 1 }; 99 | const argB = { B: 2 }; 100 | MainStamp(null, argA, argB); 101 | 102 | expect(args1[1]).toBe(argA); 103 | expect(args1[2]).toBe(argB); 104 | expect(args2[1]).toBe(argA); 105 | expect(args2[2]).toBe(argB); 106 | }); 107 | 108 | it('should push the initializer to the beginning of the list', function() { 109 | const Stamp1 = compose({ 110 | initializers: [function() {}], 111 | }); 112 | const Stamp2 = compose({ 113 | initializers: [function() {}], 114 | }); 115 | const Stamp = compose(Stamp1, InitProperty, Stamp2); 116 | 117 | expect(Stamp.compose.initializers[0]).toBe(InitProperty.compose.initializers[0]); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /packages/init-property/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const is_1 = require("@stamp/is"); 8 | const { get, ownKeys, set } = Reflect; 9 | const initializer = function initializer(opts, ref) { 10 | const args = ref.args.slice(); 11 | ownKeys(this).forEach((key) => { 12 | const stamp = get(this, key); 13 | if ((0, is_1.isStamp)(stamp)) { 14 | args[0] = opts === null || opts === void 0 ? void 0 : opts[key]; 15 | set(this, key, stamp(...args)); 16 | } 17 | }); 18 | }; 19 | /** 20 | * TODO 21 | */ 22 | const InitProperty = (0, compose_1.default)({ 23 | initializers: [initializer], 24 | composers: [ 25 | (opts) => { 26 | const { initializers } = opts.stamp.compose; 27 | initializers.splice(initializers.indexOf(initializer), 1); 28 | initializers.unshift(initializer); 29 | }, 30 | ], 31 | }); 32 | exports.default = InitProperty; 33 | // For CommonJS default export support 34 | module.exports = InitProperty; 35 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: InitProperty }); 36 | -------------------------------------------------------------------------------- /packages/init-property/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { ComposableFactoryParams, Descriptor, Initializer, Stamp } from '@stamp/compose'; 2 | import { isStamp } from '@stamp/is'; 3 | 4 | const { get, ownKeys, set } = Reflect; 5 | 6 | const initializer: Initializer = function initializer(opts, ref) { 7 | const args = ref.args.slice(); 8 | (ownKeys(this) as string[]).forEach((key) => { 9 | const stamp = get(this, key); 10 | if (isStamp(stamp)) { 11 | args[0] = opts?.[key]; 12 | set(this, key, stamp(...(args as ComposableFactoryParams))); 13 | } 14 | }); 15 | }; 16 | 17 | /** 18 | * TODO 19 | */ 20 | const InitProperty = compose({ 21 | initializers: [initializer], 22 | composers: [ 23 | (opts): void => { 24 | const { initializers } = opts.stamp.compose as Required; 25 | initializers.splice(initializers.indexOf(initializer), 1); 26 | initializers.unshift(initializer); 27 | }, 28 | ], 29 | }); 30 | 31 | export default InitProperty; 32 | 33 | // For CommonJS default export support 34 | module.exports = InitProperty; 35 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: InitProperty }); 36 | -------------------------------------------------------------------------------- /packages/init-property/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/init-property", 3 | "version": "1.0.1", 4 | "description": "Replaces properties which reference stamps with objects created from the stamps", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/init-property" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2", 18 | "@stamp/is": "^1.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/init-property/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/instanceof/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/instanceof/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/instanceof 2 | 3 | _Enables `obj instanceof MyStamp` in ES6 environments_ 4 | 5 | ```js 6 | const InstanceOf = require('@stamp/InstanceOf'); 7 | // or 8 | import InstanceOf from '@stamp/InstanceOf'; 9 | ``` 10 | 11 | ### Example 12 | 13 | Create a stamp: 14 | ```js 15 | let MyStamp = compose({ 16 | properties: { ... }, 17 | initializers: [function () { ... }] 18 | }); 19 | ``` 20 | 21 | The following doesn't work: 22 | ```js 23 | const obj = MyStamp(); 24 | obj instanceof MyStamp === false; 25 | ``` 26 | 27 | Compose the `InstanceOf` to your stamp: 28 | ```js 29 | MyStamp = MyStamp.compose(InstanceOf); 30 | ``` 31 | 32 | Now it works: 33 | ```js 34 | const myObject = MyStamp(); 35 | obj instanceof MyStamp === true; 36 | ``` 37 | 38 | 39 | ### Notes 40 | 41 | - We do not recommend to use `instanceof` in JavaScript in general. 42 | -------------------------------------------------------------------------------- /packages/instanceof/__tests__/instanceof.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const compose = require('@stamp/compose'); 4 | const InstanceOf = require('..'); 5 | 6 | describe('instanceOf', function() { 7 | if (typeof Symbol === 'undefined') return; 8 | 9 | it('should just work', function() { 10 | const MyComposedStamp = compose(InstanceOf); 11 | const obj = MyComposedStamp(); 12 | 13 | expect(obj).toBeInstanceOf(MyComposedStamp); 14 | expect(obj instanceof MyComposedStamp).toBe(true); 15 | }); 16 | 17 | it('should return false for wrong things', function() { 18 | const MyComposedStamp = compose(InstanceOf); 19 | const obj = MyComposedStamp(); 20 | 21 | expect({} instanceof MyComposedStamp).toBe(false); 22 | expect(MyComposedStamp.compose()() instanceof MyComposedStamp).toBe(false); 23 | expect(obj instanceof InstanceOf).toBe(false); 24 | expect(obj instanceof MyComposedStamp.compose()).toBe(false); 25 | expect(obj instanceof compose()).toBe(false); 26 | expect(obj instanceof RegExp).toBe(false); 27 | expect(obj instanceof Array).toBe(false); 28 | expect(obj instanceof Function).toBe(false); 29 | expect(obj instanceof String).toBe(false); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/instanceof/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const { defineProperty, set } = Reflect; 8 | const stampSymbol = Symbol.for('stamp'); 9 | /** 10 | * TODO 11 | */ 12 | const InstanceOf = (0, compose_1.default)({ 13 | methods: {}, 14 | composers: [ 15 | ({ stamp }) => { 16 | // Attaching to object prototype to save memory 17 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 18 | set(stamp.compose.methods, stampSymbol, stamp); 19 | defineProperty(stamp, Symbol.hasInstance, { 20 | value(obj) { 21 | return obj && obj[stampSymbol] === stamp; 22 | }, 23 | }); 24 | }, 25 | ], 26 | }); 27 | exports.default = InstanceOf; 28 | // For CommonJS default export support 29 | module.exports = InstanceOf; 30 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: InstanceOf }); 31 | -------------------------------------------------------------------------------- /packages/instanceof/index.ts: -------------------------------------------------------------------------------- 1 | import compose from '@stamp/compose'; 2 | 3 | const { defineProperty, set } = Reflect; 4 | 5 | const stampSymbol = Symbol.for('stamp'); 6 | 7 | interface Signature { 8 | [stampSymbol]: unknown; 9 | } 10 | 11 | /** 12 | * TODO 13 | */ 14 | const InstanceOf = compose({ 15 | methods: {}, 16 | composers: [ 17 | ({ stamp }): void => { 18 | // Attaching to object prototype to save memory 19 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 20 | set(stamp.compose.methods!, stampSymbol, stamp); 21 | 22 | defineProperty(stamp, Symbol.hasInstance, { 23 | value(obj: Signature | undefined) { 24 | return obj && obj[stampSymbol] === stamp; 25 | }, 26 | }); 27 | }, 28 | ], 29 | }); 30 | 31 | export default InstanceOf; 32 | 33 | // For CommonJS default export support 34 | module.exports = InstanceOf; 35 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: InstanceOf }); 36 | -------------------------------------------------------------------------------- /packages/instanceof/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/instanceof", 3 | "version": "1.0.1", 4 | "license": "MIT", 5 | "description": "Enables `obj instanceof MyStamp` in ES6 environments", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/instanceof" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "link": "npm run build && lerna link" 13 | }, 14 | "dependencies": { 15 | "@stamp/compose": "^1.0.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/instanceof/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/is/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/is/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/is 2 | 3 | _Various checking functions used with stamps_ 4 | 5 | ```js 6 | import {isStamp, isDescriptor, isComposable} from '@stamp/is'; 7 | // or 8 | const {isStamp, isDescriptor, isComposable} = require('@stamp/is'); 9 | ``` 10 | 11 | ### isStamp(arg) 12 | _`@stamp/is/stamp`_ 13 | 14 | Checks if passed argument is a function and has a `.compose()` property. 15 | 16 | ### isDescriptor(arg) 17 | _`@stamp/is/descriptor`_ 18 | 19 | Checks if passed argument is considered a descriptor. 20 | 21 | ### isComposable(arg) 22 | _`@stamp/is/composable`_ 23 | 24 | Checks if passed argument is considered as composable (aka stamp or descriptor). 25 | -------------------------------------------------------------------------------- /packages/is/__tests__/composable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isComposable = require('../composable'); 4 | 5 | function getStamp(obj) { 6 | const stamp = function() {}; 7 | stamp.compose = function() {}; 8 | Object.assign(stamp.compose, obj); 9 | return stamp; 10 | } 11 | 12 | describe('isComposable', function() { 13 | it('with objects', function() { 14 | const emptyStamp = getStamp(); 15 | const rawObject = {}; 16 | const rawFunction = function() {}; 17 | const regExp = /x/; 18 | 19 | expect(isComposable(emptyStamp)).toBe(true); 20 | expect(isComposable(rawObject)).toBe(true); 21 | expect(isComposable(rawFunction)).toBe(true); 22 | expect(isComposable(regExp)).toBe(true); 23 | }); 24 | 25 | it('with non-objects', function() { 26 | let undef; 27 | const NULL = null; 28 | const number = 42; 29 | const string = 's'; 30 | 31 | expect(isComposable(undef)).toBe(false); 32 | expect(isComposable(NULL)).toBe(false); 33 | expect(isComposable(number)).toBe(false); 34 | expect(isComposable(string)).toBe(false); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/is/__tests__/descriptor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isDescriptor = require('../descriptor'); 4 | 5 | function getStamp(obj) { 6 | const stamp = function() {}; 7 | stamp.compose = function() {}; 8 | Object.assign(stamp.compose, obj); 9 | return stamp; 10 | } 11 | 12 | describe('isDescriptor', function() { 13 | it('with objects', function() { 14 | const emptyStampDescriptor = getStamp().compose; 15 | const rawObject = {}; 16 | const rawFunction = function() {}; 17 | const regExp = /x/; 18 | 19 | expect(isDescriptor(emptyStampDescriptor)).toBe(true); 20 | expect(isDescriptor(rawObject)).toBe(true); 21 | expect(isDescriptor(rawFunction)).toBe(true); 22 | expect(isDescriptor(regExp)).toBe(true); 23 | }); 24 | 25 | it('with non-objects', function() { 26 | let undef; 27 | const NULL = null; 28 | const number = 42; 29 | const string = 's'; 30 | 31 | expect(isDescriptor(undef)).toBe(false); 32 | expect(isDescriptor(NULL)).toBe(false); 33 | expect(isDescriptor(number)).toBe(false); 34 | expect(isDescriptor(string)).toBe(false); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/is/__tests__/exports.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | 3 | 'use strict'; 4 | 5 | describe('exports', function() { 6 | it('isComposable', function() { 7 | expect(require('../composable')).toBe(require('../').isComposable); 8 | }); 9 | 10 | it('isDescriptor', function() { 11 | expect(require('../descriptor')).toBe(require('../').isDescriptor); 12 | }); 13 | 14 | it('isStamp', function() { 15 | expect(require('../stamp')).toBe(require('../').isStamp); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/is/__tests__/plain-object.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isPlainObject = require('../plain-object'); 4 | 5 | describe('isPlainObject', function() { 6 | it('with plain objects', function() { 7 | expect(isPlainObject({})).toBe(true); 8 | expect(isPlainObject({ a: 1 })).toBe(true); 9 | }); 10 | 11 | it('with non plain objects', function() { 12 | // eslint-disable-next-line no-array-constructor 13 | expect(isPlainObject([])).toBe(false); 14 | expect(isPlainObject([])).toBe(false); 15 | expect(isPlainObject(new Promise(function() {}))).toBe(false); 16 | expect(isPlainObject(function() {})).toBe(false); 17 | expect(isPlainObject(1)).toBe(false); 18 | expect(isPlainObject('string')).toBe(false); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/is/__tests__/stamp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isStamp = require('../stamp'); 4 | 5 | function getStamp(obj) { 6 | const stamp = function() {}; 7 | stamp.compose = function() {}; 8 | Object.assign(stamp.compose, obj); 9 | return stamp; 10 | } 11 | 12 | describe('isStamp', function() { 13 | it('with stamps', function() { 14 | const emptyStamp = getStamp(); 15 | const refsOnlyStamp = getStamp({ properties: { foo: 'bar' } }); 16 | const methodsOnlyStamp = getStamp({ methods: { method() {} } }); 17 | const closureOnlyStamp = getStamp({ initializers: [function() {}] }); 18 | 19 | expect(isStamp(emptyStamp)).toBe(true); 20 | expect(isStamp(refsOnlyStamp)).toBe(true); 21 | expect(isStamp(methodsOnlyStamp)).toBe(true); 22 | expect(isStamp(closureOnlyStamp)).toBe(true); 23 | }); 24 | 25 | it('isStamp() with non stamps', function() { 26 | let undef; 27 | const rawObject = { refs: {}, methods: {}, init: {}, compose: {}, props: {} }; 28 | const rawFunction = function() { 29 | this.init = this; 30 | }; 31 | const regExp = /x/; 32 | 33 | expect(isStamp(undef)).toBe(false); 34 | expect(isStamp(rawObject)).toBe(false); 35 | expect(isStamp(rawFunction)).toBe(false); 36 | expect(isStamp(regExp)).toBe(false); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/is/__tests__/string.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isString = require('../string'); 4 | 5 | describe('isString', function() { 6 | it('with strings', function() { 7 | expect(isString('')).toBe(true); 8 | expect(isString('my string')).toBe(true); 9 | }); 10 | 11 | it('with non plain objects', function() { 12 | expect(isString(new Array(1))).toBe(false); 13 | expect(isString([])).toBe(false); 14 | expect(isString(new Promise(function() {}))).toBe(false); 15 | expect(isString(function() {})).toBe(false); 16 | expect(isString(1)).toBe(false); 17 | expect(isString(Symbol)).toBe(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/is/array.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const { isArray: isarray } = Array; 4 | /** 5 | * @internal Checks if passed argument is considered an array. 6 | */ 7 | const isArray = isarray; 8 | exports.default = isArray; 9 | // For CommonJS default export support 10 | module.exports = isArray; 11 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isArray }); 12 | -------------------------------------------------------------------------------- /packages/is/array.ts: -------------------------------------------------------------------------------- 1 | const { isArray: isarray } = Array; 2 | 3 | /** 4 | * @internal Checks if passed argument is considered an array. 5 | */ 6 | const isArray = isarray; 7 | 8 | export default isArray; 9 | 10 | // For CommonJS default export support 11 | module.exports = isArray; 12 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isArray }); 13 | -------------------------------------------------------------------------------- /packages/is/composable.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const object_1 = __importDefault(require("./object")); 7 | /** 8 | * Checks if passed argument is considered as composable (i.e. stamp or descriptor). 9 | */ 10 | // More proper implementation would be 11 | // isDescriptor(obj) || isStamp(obj) 12 | // but there is no sense since stamp is function and function is object. 13 | // TODO: finer type guard 14 | const isComposable = object_1.default; 15 | exports.default = isComposable; 16 | // For CommonJS default export support 17 | module.exports = isComposable; 18 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isComposable }); 19 | -------------------------------------------------------------------------------- /packages/is/composable.ts: -------------------------------------------------------------------------------- 1 | import isObject from './object'; 2 | 3 | /** 4 | * Checks if passed argument is considered as composable (i.e. stamp or descriptor). 5 | */ 6 | // More proper implementation would be 7 | // isDescriptor(obj) || isStamp(obj) 8 | // but there is no sense since stamp is function and function is object. 9 | // TODO: finer type guard 10 | const isComposable = isObject; 11 | 12 | export default isComposable; 13 | 14 | // For CommonJS default export support 15 | module.exports = isComposable; 16 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isComposable }); 17 | -------------------------------------------------------------------------------- /packages/is/descriptor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const object_1 = __importDefault(require("./object")); 7 | /** 8 | * Checks if passed argument is considered a descriptor. 9 | */ 10 | // TODO: finer type guard 11 | const isDescriptor = object_1.default; 12 | exports.default = isDescriptor; 13 | // For CommonJS default export support 14 | module.exports = isDescriptor; 15 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isDescriptor }); 16 | -------------------------------------------------------------------------------- /packages/is/descriptor.ts: -------------------------------------------------------------------------------- 1 | import isObject from './object'; 2 | 3 | /** 4 | * Checks if passed argument is considered a descriptor. 5 | */ 6 | // TODO: finer type guard 7 | const isDescriptor = isObject; 8 | 9 | export default isDescriptor; 10 | 11 | // For CommonJS default export support 12 | module.exports = isDescriptor; 13 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isDescriptor }); 14 | -------------------------------------------------------------------------------- /packages/is/function.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * @internal Checks if passed argument is considered a `function`. 5 | */ 6 | const isFunction = (value) => typeof value === 'function'; 7 | exports.default = isFunction; 8 | // For CommonJS default export support 9 | module.exports = isFunction; 10 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isFunction }); 11 | -------------------------------------------------------------------------------- /packages/is/function.ts: -------------------------------------------------------------------------------- 1 | // TODO: finer type guard 2 | interface SomeFunction { 3 | (): unknown; 4 | compose?: unknown; 5 | } 6 | 7 | /** 8 | * @internal Checks if passed argument is considered a `function`. 9 | */ 10 | const isFunction = (value: unknown): value is T => typeof value === 'function'; 11 | 12 | export default isFunction; 13 | 14 | // For CommonJS default export support 15 | module.exports = isFunction; 16 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isFunction }); 17 | -------------------------------------------------------------------------------- /packages/is/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.isString = exports.isArray = exports.isPlainObject = exports.isObject = exports.isFunction = exports.isDescriptor = exports.isComposable = exports.isStamp = void 0; 7 | // Public API 8 | var stamp_1 = require("./stamp"); 9 | Object.defineProperty(exports, "isStamp", { enumerable: true, get: function () { return __importDefault(stamp_1).default; } }); 10 | var composable_1 = require("./composable"); 11 | Object.defineProperty(exports, "isComposable", { enumerable: true, get: function () { return __importDefault(composable_1).default; } }); 12 | var descriptor_1 = require("./descriptor"); 13 | Object.defineProperty(exports, "isDescriptor", { enumerable: true, get: function () { return __importDefault(descriptor_1).default; } }); 14 | // The below are private for @stamp. 15 | var function_1 = require("./function"); 16 | Object.defineProperty(exports, "isFunction", { enumerable: true, get: function () { return __importDefault(function_1).default; } }); 17 | var object_1 = require("./object"); 18 | Object.defineProperty(exports, "isObject", { enumerable: true, get: function () { return __importDefault(object_1).default; } }); 19 | var plain_object_1 = require("./plain-object"); 20 | Object.defineProperty(exports, "isPlainObject", { enumerable: true, get: function () { return __importDefault(plain_object_1).default; } }); 21 | var array_1 = require("./array"); 22 | Object.defineProperty(exports, "isArray", { enumerable: true, get: function () { return __importDefault(array_1).default; } }); 23 | var string_1 = require("./string"); 24 | Object.defineProperty(exports, "isString", { enumerable: true, get: function () { return __importDefault(string_1).default; } }); 25 | -------------------------------------------------------------------------------- /packages/is/index.ts: -------------------------------------------------------------------------------- 1 | // Public API 2 | export { default as isStamp } from './stamp'; 3 | export { default as isComposable } from './composable'; 4 | export { default as isDescriptor } from './descriptor'; 5 | 6 | // The below are private for @stamp. 7 | export { default as isFunction } from './function'; 8 | export { default as isObject } from './object'; 9 | export { default as isPlainObject } from './plain-object'; 10 | export { default as isArray } from './array'; 11 | export { default as isString } from './string'; 12 | -------------------------------------------------------------------------------- /packages/is/object.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * @internal Checks if passed argument is considered an `object`. 5 | */ 6 | const isObject = (value) => { 7 | const type = typeof value; 8 | return !!value && (type === 'object' || type === 'function'); 9 | }; 10 | exports.default = isObject; 11 | // For CommonJS default export support 12 | module.exports = isObject; 13 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isObject }); 14 | -------------------------------------------------------------------------------- /packages/is/object.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal Checks if passed argument is considered an `object`. 3 | */ 4 | const isObject = (value: unknown): value is object => { 5 | const type = typeof value; 6 | return !!value && (type === 'object' || type === 'function'); 7 | }; 8 | 9 | export default isObject; 10 | 11 | // For CommonJS default export support 12 | module.exports = isObject; 13 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isObject }); 14 | -------------------------------------------------------------------------------- /packages/is/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/is", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "description": "Various checking functions used with stamps", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/is" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "link": "npm run build && lerna link" 13 | }, 14 | "engines": { 15 | "node": ">= 10.18.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/is/plain-object.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const { prototype } = Object; 4 | const { getPrototypeOf } = Reflect; 5 | /** 6 | * @internal Checks if passed argument is a plain old javascrip object (POJO). 7 | */ 8 | const isPlainObject = (value) => !!value && typeof value === 'object' && getPrototypeOf(value) === prototype; 9 | exports.default = isPlainObject; 10 | // For CommonJS default export support 11 | module.exports = isPlainObject; 12 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isPlainObject }); 13 | -------------------------------------------------------------------------------- /packages/is/plain-object.ts: -------------------------------------------------------------------------------- 1 | const { prototype } = Object; 2 | const { getPrototypeOf } = Reflect; 3 | 4 | /** 5 | * @internal Checks if passed argument is a plain old javascrip object (POJO). 6 | */ 7 | const isPlainObject = (value: unknown): value is {} => 8 | !!value && typeof value === 'object' && getPrototypeOf(value as {}) === prototype; 9 | 10 | export default isPlainObject; 11 | 12 | // For CommonJS default export support 13 | module.exports = isPlainObject; 14 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isPlainObject }); 15 | -------------------------------------------------------------------------------- /packages/is/stamp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const function_1 = __importDefault(require("./function")); 7 | /** 8 | * Checks if passed argument is a function and has a `.compose()` property. 9 | */ 10 | // TODO: finer type guard 11 | const isStamp = (value) => (0, function_1.default)(value) && (0, function_1.default)(value.compose); 12 | exports.default = isStamp; 13 | // For CommonJS default export support 14 | module.exports = isStamp; 15 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isStamp }); 16 | -------------------------------------------------------------------------------- /packages/is/stamp.ts: -------------------------------------------------------------------------------- 1 | import isFunction from './function'; 2 | 3 | /** 4 | * Checks if passed argument is a function and has a `.compose()` property. 5 | */ 6 | // TODO: finer type guard 7 | const isStamp = (value: unknown): value is T => 8 | isFunction(value) && isFunction(value.compose); 9 | 10 | export default isStamp; 11 | 12 | // For CommonJS default export support 13 | module.exports = isStamp; 14 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isStamp }); 15 | -------------------------------------------------------------------------------- /packages/is/string.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | /** 4 | * @internal Checks if passed argument is considered a `string`. 5 | */ 6 | const isString = (value) => typeof value === 'string'; 7 | exports.default = isString; 8 | // For CommonJS default export support 9 | module.exports = isString; 10 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isString }); 11 | -------------------------------------------------------------------------------- /packages/is/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal Checks if passed argument is considered a `string`. 3 | */ 4 | const isString = (value: unknown): value is string => typeof value === 'string'; 5 | 6 | export default isString; 7 | 8 | // For CommonJS default export support 9 | module.exports = isString; 10 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: isString }); 11 | -------------------------------------------------------------------------------- /packages/is/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/it/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/it/README.md: -------------------------------------------------------------------------------- 1 | stampit 2 | # @stamp/it 3 | 4 | _Utility belt implementation of the [compose](https://github.com/stampit-org/stamp-specification) standard_ 5 | 6 | This module is the new cool modern version of the [`stampit`](https://github.com/stampit-org/stampit) module. Consider `@stamp/it` as stampit v4. 7 | 8 | ## Usage 9 | 10 | Install: 11 | ```bash 12 | $ npm i -S @stamp/it 13 | ``` 14 | 15 | Import: 16 | ```js 17 | import stampit from '@stamp/it' 18 | ``` 19 | 20 | Or import utility functions only: 21 | ```js 22 | import { 23 | methods, 24 | 25 | props, 26 | properties, 27 | 28 | statics, 29 | staticProperties, 30 | 31 | conf, 32 | configuration, 33 | 34 | deepProps, 35 | deepProperties, 36 | 37 | deepStatics, 38 | staticDeepProperties, 39 | 40 | deepConf, 41 | deepConfiguration, 42 | 43 | init, 44 | initializers, 45 | 46 | composers, 47 | 48 | propertyDescriptors, 49 | 50 | staticPropertyDescriptors 51 | } from '@stamp/it' 52 | 53 | const Stamp1 = methods({ foo() {} }) 54 | const Stamp2 = props({ bar: 'my bar' }) 55 | const Stamp3 = conf({ my: 'configuration' }) 56 | const Stamp4 = init(function (options, {stamp, instance, args}) { 57 | console.log('bla') 58 | }) 59 | ``` 60 | 61 | ## API 62 | 63 | ### Arguments 64 | 65 | See more examples in [this blog post](https://medium.com/@koresar/fun-with-stamps-episode-1-stamp-basics-e0627d81efe0). 66 | 67 | Create a new empty stamp: 68 | ```js 69 | const S1 = stampit() 70 | ``` 71 | 72 | Create a new stamp with a method: 73 | ```js 74 | const S2 = stampit({ 75 | methods: { 76 | myMethod() { } 77 | } 78 | }) 79 | ``` 80 | 81 | Create a new stamp with a property: 82 | ```js 83 | const S3 = stampit({ 84 | props: { // or properties: 85 | myProperty: 42 86 | } 87 | }) 88 | ``` 89 | 90 | Create a new stamp with a static property: 91 | ```js 92 | const S4 = stampit({ 93 | statics: { // or staticProperties: 94 | myStaticProperty: 42 95 | } 96 | }) 97 | ``` 98 | 99 | Create a new stamp with initializer(s): 100 | ```js 101 | const S5 = stampit({ 102 | init: function () { console.log('hi form initializer') } 103 | }) 104 | // or 105 | const S6 = stampit({ 106 | init: [ // or initializers: 107 | function () { }, 108 | function () { } 109 | ] 110 | }) 111 | ``` 112 | 113 | Create a new stamp with a configuration: 114 | ```js 115 | const S7 = stampit({ 116 | conf: { // or configuration: 117 | myConfiguration: { anything: 'here' } 118 | } 119 | }) 120 | ``` 121 | 122 | Compose the stamps above together: 123 | ```js 124 | const Stamp = compose(S1, S2, S3, S4, S5, S6, S7) 125 | console.log(Stamp) 126 | // { compose: [function] { properties, methods, staticProperties, initializers, configuration } } 127 | 128 | const obj = Stamp() 129 | console.log(obj) 130 | // { myProperty: 42 } 131 | console.log(Object.getPrototypeOf(obj)) 132 | // { myMethod: [function] } 133 | ``` 134 | 135 | See more examples in [this blog post](https://medium.com/@koresar/fun-with-stamps-episode-1-stamp-basics-e0627d81efe0). 136 | 137 | 138 | ### Static methods 139 | 140 | #### The Stamp.create() static method 141 | 142 | Calling `Stamp.create()` is identical to calling `Stamp()`. 143 | 144 | #### Shortcut static methods 145 | 146 | All stamps receive few static properties. 147 | These are taken from the [`@stamp/shortcut`](https://github.com/stampit-org/stamp/blob/master/packages/shortcut/README.md) stamp. 148 | For example: 149 | ```js 150 | stampit() 151 | .props({ foo: 'foo' }) 152 | .deepConf({ things: ['bar'] }) 153 | .methods({ baz() {} }) 154 | // etc 155 | ``` 156 | 157 | 158 | ### Shortcut exported methods 159 | 160 | The module exports a range of shortcut methods. 161 | These are taken from the [`@stamp/shortcut`](https://github.com/stampit-org/stamp/blob/master/packages/shortcut/README.md) stamp. 162 | ```js 163 | import {methods, props, init, statics, /* etc */} from '@stamp/it' 164 | ``` 165 | Each returns a stampit-flavoured stamp. 166 | 167 | NOTE! Unlike the [`@stamp/shortcut`](https://www.npmjs.com/package/@stamp/shortcut) module, all the exported functions of `@stamp/it` are stampit-flavoured. Meaning that: 168 | ```js 169 | import {methods} from '@stamp/it' 170 | const Stamp = methods({ foo() {} }) 171 | .props({bar: 1}) // THIS WILL WORK 172 | .statics({baz: 2}) // AND THIS WILL WORK TOO 173 | ``` 174 | -------------------------------------------------------------------------------- /packages/it/__tests__/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable jest/expect-expect */ 3 | /* eslint-disable jest/no-test-return-statement */ 4 | /* eslint-disable node/no-unpublished-require */ 5 | 6 | 'use strict'; 7 | 8 | const checkCompose = require('@stamp/check-compose'); 9 | const stampit = require('../'); 10 | 11 | describe('@stamp/it', function() { 12 | it('passes official tests', function() { 13 | const compose = require('..'); 14 | return checkCompose(compose).then(function(result) { 15 | const { failures } = result; 16 | if (failures && failures.length > 0) { 17 | const errorString = failures 18 | .map(function(f) { 19 | return JSON.stringify(f); 20 | }) 21 | .join('\n'); 22 | throw new Error(errorString); 23 | } 24 | }); 25 | }); 26 | 27 | it('has the .create static function', function() { 28 | const stamp = stampit({ 29 | methods: { 30 | foo: function foo() { 31 | return 'foo'; 32 | }, 33 | }, 34 | properties: { 35 | bar: 'bar', 36 | }, 37 | }); 38 | 39 | expect(stamp.create()).toStrictEqual(stamp()); 40 | expect(stamp.create().foo()).toBe('foo'); 41 | }); 42 | 43 | it('converts extended descriptor to a standard', function() { 44 | const initializer = function() {}; 45 | const composer = function() {}; 46 | const descr = stampit({ 47 | props: { p: 1 }, 48 | init: initializer, 49 | composers: composer, 50 | deepProps: { dp: 1 }, 51 | statics: { s: 1 }, 52 | deepStatics: { ds: 1 }, 53 | conf: { c: 1 }, 54 | deepConf: { dc: 1 }, 55 | propertyDescriptors: { pd: { value: 1 } }, 56 | staticPropertyDescriptors: { spd: { value: 1 } }, 57 | name: '1', 58 | }).compose; 59 | 60 | expect(descr.properties.p).toBe(1); 61 | expect(descr.initializers).toContain(initializer); 62 | expect(descr.composers).toContain(composer); 63 | expect(descr.deepProperties.dp).toBe(1); 64 | expect(descr.staticProperties.s).toBe(1); 65 | expect(descr.staticDeepProperties.ds).toBe(1); 66 | expect(descr.configuration.c).toBe(1); 67 | expect(descr.deepConfiguration.dc).toBe(1); 68 | expect(descr.propertyDescriptors.pd.value).toBe(1); 69 | expect(descr.staticPropertyDescriptors.spd.value).toBe(1); 70 | expect(descr.staticPropertyDescriptors.name.value).toBe('1'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/it/__tests__/shortcut.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable node/no-unpublished-require */ 2 | 3 | 'use strict'; 4 | 5 | const _ = require('lodash'); 6 | const stampit = require('../'); 7 | 8 | // stampit.methods, stampit.statics, stampit.init, stampit.props, etc. 9 | describe('shortcuts', function() { 10 | it('stampit.methods shortcut', function() { 11 | const methods = { method1() {} }; 12 | const stamp1 = stampit({ methods }); 13 | const stamp2 = stampit.methods(methods); 14 | 15 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 16 | }); 17 | 18 | it('stampit.init shortcut', function() { 19 | const init = function() {}; 20 | const stamp1 = stampit({ init }); 21 | const stamp2 = stampit.init(init); 22 | 23 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 24 | }); 25 | 26 | it('stampit.composers shortcut', function() { 27 | const composer = function() {}; 28 | const stamp1 = stampit({ composers: composer }); 29 | const stamp2 = stampit.composers(composer); 30 | 31 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 32 | }); 33 | 34 | it('stampit.props shortcut', function() { 35 | const props = { method1() {} }; 36 | const stamp1 = stampit({ props }); 37 | const stamp2 = stampit.props(props); 38 | 39 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 40 | }); 41 | 42 | it('stampit.statics shortcut', function() { 43 | const statics = { method1() {} }; 44 | const stamp1 = stampit({ statics }); 45 | const stamp2 = stampit.statics(statics); 46 | 47 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 48 | }); 49 | 50 | it('stampit.propertyDescriptors shortcut', function() { 51 | const propertyDescriptors = { x: { writable: true } }; 52 | const stamp1 = stampit({ propertyDescriptors }); 53 | const stamp2 = stampit.propertyDescriptors(propertyDescriptors); 54 | 55 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 56 | }); 57 | 58 | it('stampit.staticPropertyDescriptors shortcut', function() { 59 | const staticPropertyDescriptors = { x: { writable: true } }; 60 | const stamp1 = stampit({ staticPropertyDescriptors }); 61 | const stamp2 = stampit.staticPropertyDescriptors(staticPropertyDescriptors); 62 | 63 | expect(_.toPlainObject(stamp1.compose)).toStrictEqual(_.toPlainObject(stamp2.compose)); 64 | }); 65 | 66 | it('invoke should not matter', function() { 67 | // eslint-disable-next-line guard-for-in,no-restricted-syntax 68 | for (const prop in stampit) { 69 | const func = stampit[prop]; 70 | const descriptor1 = _.toPlainObject(func().compose); 71 | const descriptor2 = _.toPlainObject(stampit[prop]().compose); 72 | 73 | expect(descriptor1).toStrictEqual(descriptor2); 74 | } 75 | }); 76 | 77 | it('all shortcuts combined', function() { 78 | const { compose } = stampit; 79 | const { methods } = stampit; 80 | const { init } = stampit; 81 | 82 | const HasFoo = compose({ 83 | properties: { 84 | foo: 'default foo!', 85 | }, 86 | }) 87 | .methods() 88 | .properties() 89 | .initializers() 90 | .deepProperties() 91 | .staticProperties() 92 | .staticDeepProperties() 93 | .configuration() 94 | .deepConfiguration() 95 | .propertyDescriptors() 96 | .staticPropertyDescriptors() 97 | .props() 98 | .init() 99 | .composers() 100 | .deepProps() 101 | .statics() 102 | .deepStatics() 103 | .conf() 104 | .deepConf(); 105 | 106 | const PrintFoo = methods({ 107 | printFoo() { 108 | // console.log(this.foo || 'There is no foo'); 109 | }, 110 | }) 111 | .methods() 112 | .properties() 113 | .initializers() 114 | .deepProperties() 115 | .staticProperties() 116 | .staticDeepProperties() 117 | .configuration() 118 | .deepConfiguration() 119 | .propertyDescriptors() 120 | .staticPropertyDescriptors() 121 | .props() 122 | .init() 123 | .composers() 124 | .deepProps() 125 | .statics() 126 | .deepStatics() 127 | .conf() 128 | .deepConf(); 129 | 130 | const Init = init(function(opts) { 131 | this.foo = opts.foo; 132 | }) 133 | .methods() 134 | .properties() 135 | .initializers() 136 | .deepProperties() 137 | .staticProperties() 138 | .staticDeepProperties() 139 | .configuration() 140 | .deepConfiguration() 141 | .propertyDescriptors() 142 | .staticPropertyDescriptors() 143 | .props() 144 | .init() 145 | .composers() 146 | .deepProps() 147 | .statics() 148 | .deepStatics() 149 | .conf() 150 | .deepConf(); 151 | 152 | const Foo = compose(HasFoo, PrintFoo, Init); 153 | 154 | expect(Foo.compose.properties.foo).toBe('default foo!'); 155 | // eslint-disable-next-line jest/no-truthy-falsy 156 | expect(typeof Foo.compose.methods.printFoo === 'function').toBeTruthy(); 157 | expect(Foo.compose.initializers).toHaveLength(1); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /packages/it/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | /* eslint @typescript-eslint/no-use-before-define: ["error", { "variables": false }] */ 7 | const compose_1 = __importDefault(require("@stamp/compose")); 8 | const core_1 = require("@stamp/core"); 9 | const is_1 = require("@stamp/is"); 10 | const shortcut_1 = __importDefault(require("@stamp/shortcut")); 11 | const { concat } = Array.prototype; 12 | const { get, ownKeys, set } = Reflect; 13 | const extractFunctions = (...args) => { 14 | const fns = concat.apply([], [...args]).filter(is_1.isFunction); 15 | return fns.length === 0 ? undefined : fns; 16 | }; 17 | const standardiseDescriptor = (descr) => { 18 | if (!(0, is_1.isObject)(descr)) 19 | return descr; 20 | const { methods, properties, props, initializers, init, composers, deepProperties, deepProps, propertyDescriptors, staticProperties, statics, staticDeepProperties, deepStatics, configuration, conf, deepConfiguration, deepConf, } = descr; 21 | const p = (0, is_1.isObject)(props) || (0, is_1.isObject)(properties) ? (0, core_1.assign)({}, props, properties) : undefined; 22 | const dp = (0, is_1.isObject)(deepProps) || (0, is_1.isObject)(deepProperties) ? (0, core_1.merge)({}, deepProps, deepProperties) : undefined; 23 | const sp = (0, is_1.isObject)(statics) || (0, is_1.isObject)(staticProperties) ? (0, core_1.assign)({}, statics, staticProperties) : undefined; 24 | const sdp = (0, is_1.isObject)(deepStatics) || (0, is_1.isObject)(staticDeepProperties) ? (0, core_1.merge)({}, deepStatics, staticDeepProperties) : undefined; 25 | let spd = descr.staticPropertyDescriptors; 26 | if ((0, is_1.isString)(descr.name)) 27 | spd = (0, core_1.assign)({}, spd || {}, { name: { value: descr.name } }); 28 | const c = (0, is_1.isObject)(conf) || (0, is_1.isObject)(configuration) ? (0, core_1.assign)({}, conf, configuration) : undefined; 29 | const dc = (0, is_1.isObject)(deepConf) || (0, is_1.isObject)(deepConfiguration) ? (0, core_1.merge)({}, deepConf, deepConfiguration) : undefined; 30 | const ii = extractFunctions(init, initializers); 31 | const cc = extractFunctions(composers); 32 | const descriptor = {}; 33 | if (methods) 34 | descriptor.methods = methods; 35 | if (p) 36 | descriptor.properties = p; 37 | if (ii) 38 | descriptor.initializers = ii; 39 | if (cc) 40 | descriptor.composers = cc; 41 | if (dp) 42 | descriptor.deepProperties = dp; 43 | if (sp) 44 | descriptor.staticProperties = sp; 45 | if (sdp) 46 | descriptor.staticDeepProperties = sdp; 47 | if (propertyDescriptors) 48 | descriptor.propertyDescriptors = propertyDescriptors; 49 | if (spd) 50 | descriptor.staticPropertyDescriptors = spd; 51 | if (c) 52 | descriptor.configuration = c; 53 | if (dc) 54 | descriptor.deepConfiguration = dc; 55 | return descriptor; 56 | }; 57 | // { (...args: any[]): StampWithShortcuts; compose: ComposeMethod & Descriptor; } 58 | const stampit = function stampit(...args) { 59 | return compose_1.default.apply(this || baseStampit, args.map((arg) => ((0, is_1.isStamp)(arg) ? arg : standardiseDescriptor(arg)))); 60 | }; 61 | const baseStampit = shortcut_1.default.compose({ 62 | staticProperties: { 63 | create(...args) { 64 | return this.apply(this, args); 65 | }, 66 | compose: stampit, // infecting 67 | }, 68 | }); 69 | const shortcuts = shortcut_1.default.compose.staticProperties; 70 | ownKeys(shortcuts).forEach((prop) => set(stampit, prop, get(shortcuts, prop).bind(baseStampit))); 71 | stampit.compose = stampit.bind(undefined); 72 | exports.default = stampit; 73 | // For CommonJS default export support 74 | module.exports = stampit; 75 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: stampit }); 76 | -------------------------------------------------------------------------------- /packages/it/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-use-before-define: ["error", { "variables": false }] */ 2 | import compose, { Composable, Composer, Descriptor, Initializer, PropertyMap } from '@stamp/compose'; 3 | import { assign, merge } from '@stamp/core'; 4 | import { isFunction, isObject, isStamp, isString } from '@stamp/is'; 5 | import Shortcut, { ComposeProperty, StampWithShortcuts } from '@stamp/shortcut'; 6 | 7 | const { concat } = Array.prototype; 8 | const { get, ownKeys, set } = Reflect; 9 | 10 | interface ExtractFunctions { 11 | (...args: unknown[]): Composer[] | Initializer[] | undefined; 12 | } 13 | const extractFunctions: ExtractFunctions = (...args) => { 14 | const fns = concat.apply([], [...args]).filter(isFunction) as Composer[] | Initializer[]; 15 | return fns.length === 0 ? undefined : fns; 16 | }; 17 | 18 | interface ExtendedDescriptor extends Descriptor { 19 | props?: Descriptor['properties']; 20 | init?: Descriptor['initializers']; 21 | deepProps?: Descriptor['deepProperties']; 22 | statics?: Descriptor['staticProperties']; 23 | deepStatics?: Descriptor['staticDeepProperties']; 24 | conf?: Descriptor['configuration']; 25 | deepConf?: Descriptor['deepConfiguration']; 26 | name?: string; 27 | } 28 | 29 | interface StandardiseDescriptor { 30 | (descr: ExtendedDescriptor | undefined): Descriptor | undefined; 31 | } 32 | const standardiseDescriptor: StandardiseDescriptor = (descr) => { 33 | if (!isObject(descr)) return descr; 34 | 35 | const { 36 | methods, 37 | properties, 38 | props, 39 | initializers, 40 | init, 41 | composers, 42 | deepProperties, 43 | deepProps, 44 | propertyDescriptors, 45 | staticProperties, 46 | statics, 47 | staticDeepProperties, 48 | deepStatics, 49 | configuration, 50 | conf, 51 | deepConfiguration, 52 | deepConf, 53 | } = descr; 54 | 55 | const p = isObject(props) || isObject(properties) ? assign({}, props, properties) : undefined; 56 | const dp = isObject(deepProps) || isObject(deepProperties) ? merge({}, deepProps, deepProperties) : undefined; 57 | const sp = isObject(statics) || isObject(staticProperties) ? assign({}, statics, staticProperties) : undefined; 58 | const sdp = 59 | isObject(deepStatics) || isObject(staticDeepProperties) ? merge({}, deepStatics, staticDeepProperties) : undefined; 60 | 61 | let spd = descr.staticPropertyDescriptors; 62 | if (isString(descr.name)) spd = assign({}, spd || {}, { name: { value: descr.name } }); 63 | 64 | const c = isObject(conf) || isObject(configuration) ? assign({}, conf, configuration) : undefined; 65 | const dc = isObject(deepConf) || isObject(deepConfiguration) ? merge({}, deepConf, deepConfiguration) : undefined; 66 | const ii = extractFunctions(init, initializers) as Initializer[] | undefined; 67 | const cc = extractFunctions(composers) as Composer[] | undefined; 68 | 69 | const descriptor: Descriptor = {}; 70 | if (methods) descriptor.methods = methods; 71 | if (p) descriptor.properties = p; 72 | if (ii) descriptor.initializers = ii; 73 | if (cc) descriptor.composers = cc; 74 | if (dp) descriptor.deepProperties = dp; 75 | if (sp) descriptor.staticProperties = sp; 76 | if (sdp) descriptor.staticDeepProperties = sdp; 77 | if (propertyDescriptors) descriptor.propertyDescriptors = propertyDescriptors; 78 | if (spd) descriptor.staticPropertyDescriptors = spd; 79 | if (c) descriptor.configuration = c; 80 | if (dc) descriptor.deepConfiguration = dc; 81 | 82 | return descriptor; 83 | }; 84 | 85 | /** 86 | * TODO 87 | * 88 | * @interface StampIt 89 | * @extends {StampWithShortcuts} 90 | */ 91 | interface StampIt extends StampWithShortcuts { 92 | (this: unknown, ...args: (ExtendedDescriptor | StampWithShortcuts)[]): StampWithShortcuts; 93 | } 94 | // { (...args: any[]): StampWithShortcuts; compose: ComposeMethod & Descriptor; } 95 | const stampit = function stampit(this: StampIt, ...args: (Composable | undefined)[]) { 96 | return compose.apply( 97 | this || baseStampit, 98 | args.map((arg) => (isStamp(arg) ? arg : standardiseDescriptor(arg))) 99 | ); 100 | } as StampIt; 101 | 102 | const baseStampit = Shortcut.compose({ 103 | staticProperties: { 104 | create(this: StampWithShortcuts, ...args: [(object | undefined)?, ...unknown[]]): object { 105 | return this.apply(this, args); 106 | }, 107 | compose: stampit, // infecting 108 | }, 109 | }); 110 | 111 | const shortcuts = Shortcut.compose.staticProperties as PropertyMap; 112 | ownKeys(shortcuts).forEach((prop) => set(stampit, prop, get(shortcuts, prop).bind(baseStampit))); 113 | stampit.compose = (stampit.bind(undefined) as unknown) as ComposeProperty; 114 | 115 | export default stampit; 116 | 117 | // For CommonJS default export support 118 | module.exports = stampit; 119 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: stampit }); 120 | -------------------------------------------------------------------------------- /packages/it/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/it", 3 | "version": "1.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@stamp/it", 9 | "version": "1.1.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "lodash": "^4.17.15" 13 | }, 14 | "engines": { 15 | "node": ">= 10.18.0" 16 | } 17 | }, 18 | "node_modules/lodash": { 19 | "version": "4.17.15", 20 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 21 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", 22 | "dev": true 23 | } 24 | }, 25 | "dependencies": { 26 | "lodash": { 27 | "version": "4.17.15", 28 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 29 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", 30 | "dev": true 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/it/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/it", 3 | "version": "1.1.0", 4 | "description": "A nice, handy API implementation of the compose standard", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/it" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "engines": { 17 | "node": ">= 10.18.0" 18 | }, 19 | "dependencies": { 20 | "@stamp/compose": "^1.0.2", 21 | "@stamp/core": "^1.0.1", 22 | "@stamp/is": "^1.0.0", 23 | "@stamp/shortcut": "^1.0.2" 24 | }, 25 | "devDependencies": { 26 | "@stamp/check-compose": "^1.0.3", 27 | "lodash": "^4.17.15" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/it/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/named/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/named 2 | 3 | _Changes the `Stamp.name` property using the [new ES6 feature](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name)._ 4 | 5 | [Supported platforms](http://kangax.github.io/compat-table/es6/#test-function_name_property_isn't_writable,_is_configurable): node>=4, iOS>=10, Edge, FF, Chrome, Safari 6 | 7 | If used in a non-supported environment (node { 78 | instance.stampConfiguration = stamp.compose.configuration; 79 | instance.stampDeepConfiguration = stamp.compose.deepConfiguration; 80 | }] 81 | }) 82 | export default ConfiguredPrivatize; 83 | ``` 84 | 85 | Then you can use for your other stamps and being able to access configuration within any method while still have it hidden from public sight. 86 | 87 | ```js 88 | compose(ConfiguredPrivatize, { 89 | configuration: { 90 | secret: 'mykey' 91 | }, 92 | methods: { 93 | encrypt(value) { 94 | const { secret } = this.stampConfiguration; 95 | // run encryption with secret while it's still hidden from outside 96 | } 97 | } 98 | }) 99 | ``` 100 | 101 | ## Development 102 | 103 | This `@stamp/privatize` module knows about and relies on other ecosystem stamps implementation. This means that other `@stamp/*` ecosystem stamps should know nothing about `@stamp/privatize`. But opposite is acceptable. 104 | -------------------------------------------------------------------------------- /packages/privatize/__tests__/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable node/no-unpublished-require */ 2 | 3 | 'use strict'; 4 | 5 | const compose = require('@stamp/compose'); 6 | const InstanceOf = require('@stamp/instanceof'); 7 | const Privatize = require('..'); 8 | 9 | describe('@stamp/privatize', function() { 10 | it('applies access restrictions', function() { 11 | let accessFoo; 12 | let accessBar; 13 | const Orig = compose({ 14 | properties: { foo: 1 }, 15 | methods: { 16 | bar() {}, 17 | checkAccess() { 18 | accessFoo = this.foo; 19 | accessBar = this.bar; 20 | }, 21 | }, 22 | }); 23 | const Stamp = Orig.compose(Privatize).privatizeMethods('bar'); 24 | const instance = Stamp(); 25 | 26 | expect(instance.foo).toBeUndefined(); 27 | expect(instance.bar).toBeUndefined(); 28 | instance.checkAccess(); 29 | expect(accessFoo).toBe(1); 30 | expect(accessBar).toBe(Orig.compose.methods.bar); 31 | }); 32 | 33 | it('should push the initializer to the end of the list', function() { 34 | const Stamp1 = compose({ 35 | initializers: [function() {}], 36 | }); 37 | const Stamp2 = compose({ 38 | initializers: [function() {}], 39 | }); 40 | const Stamp = compose(Stamp1, Privatize, Stamp2); 41 | 42 | expect(Stamp.compose.initializers[2]).toBe(Privatize.compose.initializers[0]); 43 | }); 44 | 45 | it('should allow rubbish to the .privatizeMethods()', function() { 46 | const Stamp = compose({ methods: { bar() {} } }, Privatize).privatizeMethods( 47 | null, 48 | {}, 49 | [], 50 | 1, 51 | 'bar', 52 | NaN, 53 | /a/, 54 | undefined 55 | ); 56 | const instance = Stamp(); 57 | 58 | expect(instance.bar).toBeUndefined(); 59 | }); 60 | 61 | it('should work without any methods defined', function() { 62 | const Stamp = compose(Privatize, { 63 | properties: { bar: 'foo' }, 64 | }); 65 | const instance = Stamp(); 66 | 67 | expect(instance.bar).toBeUndefined(); 68 | }); 69 | 70 | it('can be used as a standalone function', function() { 71 | const { privatizeMethods } = Privatize; 72 | let accessFoo; 73 | let accessBar; 74 | const Orig = compose({ 75 | properties: { foo: 1 }, 76 | methods: { 77 | bar() {}, 78 | checkAccess() { 79 | accessFoo = this.foo; 80 | accessBar = this.bar; 81 | }, 82 | }, 83 | }); 84 | const Stamp = privatizeMethods('bar').compose(Orig); 85 | const instance = Stamp(); 86 | 87 | expect(instance.foo).toBeUndefined(); 88 | expect(instance.bar).toBeUndefined(); 89 | instance.checkAccess(); 90 | expect(accessFoo).toBe(1); 91 | expect(accessBar).toBe(Orig.compose.methods.bar); 92 | }); 93 | 94 | it('works with InstanceOf stamp', function() { 95 | const Stamp = compose(InstanceOf, Privatize); 96 | expect(Stamp() instanceof Stamp).toBe(true); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /packages/privatize/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const { defineProperty, get, ownKeys, set } = Reflect; 8 | const stampSymbol = Symbol.for('stamp'); 9 | const privates = new WeakMap(); // WeakMap works in IE11, node 0.12 10 | const makeProxyFunction = function makeProxyFunction(fn, name) { 11 | function proxiedFn(...args) { 12 | return fn.apply(privates.get(this), args); 13 | } 14 | defineProperty(proxiedFn, 'name', { 15 | value: name, 16 | configurable: true, 17 | }); 18 | return proxiedFn; 19 | }; 20 | const initializer = function initializer(_, opts) { 21 | const descriptor = opts.stamp.compose; 22 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 23 | const privateMethodKeys = descriptor.deepConfiguration.Privatize.methods; 24 | const newObject = {}; // our proxy object 25 | privates.set(newObject, this); 26 | const { methods } = descriptor; 27 | if (methods) { 28 | ownKeys(methods).forEach((name) => { 29 | if (privateMethodKeys.indexOf(name) < 0) { 30 | // not private, thus wrap 31 | set(newObject, name, makeProxyFunction(get(methods, name), name)); 32 | } 33 | }); 34 | // Integration with @stamp/instanceof 35 | if (get(methods, stampSymbol)) 36 | set(newObject, stampSymbol, opts.stamp); 37 | } 38 | return newObject; 39 | }; 40 | /** 41 | * TODO 42 | */ 43 | const Privatize = (0, compose_1.default)({ 44 | initializers: [initializer], 45 | deepConfiguration: { Privatize: { methods: [] } }, 46 | staticProperties: { 47 | privatizeMethods(...args) { 48 | const methodNames = []; 49 | args.forEach((arg) => { 50 | if (typeof arg === 'string' && arg.length > 0) { 51 | methodNames.push(arg); 52 | } 53 | }); 54 | return ((this === null || this === void 0 ? void 0 : this.compose) ? this : Privatize).compose({ 55 | deepConfiguration: { 56 | Privatize: { 57 | methods: methodNames, 58 | }, 59 | }, 60 | }); 61 | }, 62 | }, 63 | composers: [ 64 | ((opts) => { 65 | const { initializers } = opts.stamp.compose; 66 | // Keep our initializer the last to return proxy object 67 | initializers.splice(initializers.indexOf(initializer), 1); 68 | initializers.push(initializer); 69 | }), 70 | ], 71 | }); 72 | exports.default = Privatize; 73 | // For CommonJS default export support 74 | module.exports = Privatize; 75 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: Privatize }); 76 | -------------------------------------------------------------------------------- /packages/privatize/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { Composer, Descriptor, Initializer, PropertyMap, Stamp } from '@stamp/compose'; 2 | 3 | const { defineProperty, get, ownKeys, set } = Reflect; 4 | 5 | const stampSymbol = Symbol.for('stamp'); 6 | 7 | const privates = new WeakMap(); // WeakMap works in IE11, node 0.12 8 | 9 | const makeProxyFunction = function makeProxyFunction( 10 | this: unknown, 11 | fn: T, 12 | name: PropertyKey 13 | ): (this: object, ...args: unknown[]) => unknown { 14 | function proxiedFn(this: object, ...args: unknown[]): unknown { 15 | return fn.apply(privates.get(this), args); 16 | } 17 | 18 | defineProperty(proxiedFn, 'name', { 19 | value: name, 20 | configurable: true, 21 | }); 22 | 23 | return proxiedFn; 24 | }; 25 | 26 | interface PrivatizeDescriptor extends Descriptor { 27 | deepConfiguration?: PropertyMap & { Privatize: { methods: PropertyKey[] } }; 28 | } 29 | 30 | const initializer: Initializer = function initializer(_, opts) { 31 | const descriptor = opts.stamp.compose as PrivatizeDescriptor; 32 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 33 | const privateMethodKeys = descriptor.deepConfiguration!.Privatize.methods; 34 | 35 | const newObject = {}; // our proxy object 36 | privates.set(newObject, this); 37 | 38 | const { methods } = descriptor; 39 | if (methods) { 40 | ownKeys(methods).forEach((name) => { 41 | if (privateMethodKeys.indexOf(name) < 0) { 42 | // not private, thus wrap 43 | set(newObject, name, makeProxyFunction(get(methods, name), name)); 44 | } 45 | }); 46 | 47 | // Integration with @stamp/instanceof 48 | if (get(methods, stampSymbol)) set(newObject, stampSymbol, opts.stamp); 49 | } 50 | 51 | return newObject; 52 | }; 53 | 54 | /** 55 | * TODO 56 | */ 57 | const Privatize = compose({ 58 | initializers: [initializer], 59 | deepConfiguration: { Privatize: { methods: [] } }, 60 | staticProperties: { 61 | privatizeMethods(this: Stamp | undefined, ...args: string[]): Stamp { 62 | const methodNames: string[] = []; 63 | args.forEach((arg) => { 64 | if (typeof arg === 'string' && arg.length > 0) { 65 | methodNames.push(arg); 66 | } 67 | }); 68 | return (this?.compose ? this : Privatize).compose({ 69 | deepConfiguration: { 70 | Privatize: { 71 | methods: methodNames, 72 | }, 73 | }, 74 | }); 75 | }, 76 | }, 77 | composers: [ 78 | ((opts) => { 79 | const { initializers } = opts.stamp.compose as Required; 80 | // Keep our initializer the last to return proxy object 81 | initializers.splice(initializers.indexOf(initializer), 1); 82 | initializers.push(initializer); 83 | }) as Composer, 84 | ], 85 | }); 86 | 87 | export default Privatize; 88 | 89 | // For CommonJS default export support 90 | module.exports = Privatize; 91 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: Privatize }); 92 | -------------------------------------------------------------------------------- /packages/privatize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/privatize", 3 | "version": "1.0.3", 4 | "description": "Protect private properties", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/privatize" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2" 18 | }, 19 | "devDependencies": { 20 | "@stamp/instanceof": "^1.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/privatize/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/required/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/required/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/required 2 | 3 | _Insist on a method/property/staticProperty/configuration presence_ 4 | 5 | This stamp (aka behavior) will throw if a method (property, staticProperty, configuration, etc) is missing at object creation. 6 | 7 | ## Usage 8 | 9 | ```js 10 | import Required from '@stamp/required' 11 | 12 | const InsistOnRedrawMethod = Required.required({methods: {redraw: Required}}) 13 | ``` 14 | 15 | Or if you don't want to import the stamp you can import only the method: 16 | ```js 17 | import {required} from '@stamp/required' 18 | const InsistOnRedrawMethod = required({methods: {redraw: required}}) 19 | ``` 20 | 21 | ## API 22 | 23 | ### Static methods 24 | 25 | #### required 26 | Setup which things are required 27 | `stamp.required({METATYPE: {KEY: required}}) -> Stamp` 28 | 29 | 30 | ## Example 31 | 32 | ```js 33 | import stampit from '@stamp/it' 34 | import Required, {required} from '@stamp/required' 35 | 36 | const requiredConfigurationDifficulty = required({configuration: {difficulty: required}}) 37 | const requiredMethodMove = required({methods: {move: Required}}) 38 | const requiredPropertyGuild = Required.required({properties: {guild: required}}) 39 | 40 | let Paladin = stampit({ 41 | props: { 42 | mana: 50, 43 | strength: 50, 44 | health: 100 45 | } 46 | }) 47 | 48 | const paladin = Paladin() // ok 49 | 50 | Paladin = Paladin.compose( 51 | requiredConfigurationDifficulty, 52 | requiredMethodMove, 53 | requiredPropertyGuild 54 | ) 55 | 56 | const paladin = Paladin() // THROWS - Required: There must be difficulty in this stamp configuration 57 | 58 | Paladin = Paladin.conf({ difficulty: 5 }) 59 | 60 | const paladin = Paladin() // THROWS - Required: There must be move in this stamp methods 61 | 62 | Paladin = Paladin.methods({ 63 | move(x, y) { 64 | // ... implementation 65 | } 66 | }) 67 | 68 | const paladin = Paladin() // THROWS - Required: There must be guild in this stamp properties 69 | 70 | Paladin = Paladin.props({ guild: 'Warriors of Light' }) 71 | 72 | const paladin = Paladin() // ok 73 | ``` 74 | -------------------------------------------------------------------------------- /packages/required/__tests__/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const compose = require('@stamp/compose'); 4 | const Required = require('..'); 5 | 6 | const { required } = Required; 7 | 8 | describe('@stamp/required', function() { 9 | it('should throw if a required method is missing on object creation', function() { 10 | const Stamp = compose(Required).required({ 11 | methods: { 12 | requireMe: required, 13 | }, 14 | }); 15 | 16 | expect(function() { 17 | Stamp(); 18 | }).toThrow(/Required: There must be requireMe in this stamp methods/); 19 | }); 20 | 21 | it('should make sure static method works standalone', function() { 22 | const Stamp = required({ 23 | methods: { 24 | requireMe: required, 25 | }, 26 | }); 27 | 28 | expect(function() { 29 | Stamp(); 30 | }).toThrow(/Required: There must be requireMe in this stamp methods/); 31 | }); 32 | 33 | it('should NOT throw if a required things present on object creation', function() { 34 | const Stamp = required({ 35 | methods: { 36 | requireMe: required, 37 | }, 38 | properties: { 39 | requireMe: required, 40 | }, 41 | configuration: { 42 | requireMe: required, 43 | }, 44 | staticProperties: { 45 | requireMe: required, 46 | }, 47 | propertyDescriptors: NaN, 48 | staticDeepProperties: undefined, 49 | staticPropertyDescriptors: null, 50 | deepConfiguration: {}, 51 | }).compose({ 52 | methods: { 53 | requireMe() {}, 54 | }, 55 | properties: { 56 | requireMe: null, 57 | }, 58 | configuration: { 59 | requireMe: 0, 60 | }, 61 | staticProperties: { 62 | requireMe: NaN, 63 | }, 64 | }); 65 | 66 | expect(function() { 67 | Stamp(); 68 | }).not.toThrow(); 69 | }); 70 | 71 | it('should work for all meta data things', function() { 72 | let Stamp = required({ 73 | methods: { 74 | requireMe: required, 75 | }, 76 | }); 77 | expect(function() { 78 | Stamp(); 79 | }).toThrow(/Required: There must be requireMe in this stamp methods/); 80 | 81 | Stamp = required({ 82 | properties: { 83 | requireMe: required, 84 | }, 85 | }); 86 | expect(function() { 87 | Stamp(); 88 | }).toThrow(/Required: There must be requireMe in this stamp properties/); 89 | 90 | Stamp = required({ 91 | configuration: { 92 | requireMe: required, 93 | }, 94 | }); 95 | expect(function() { 96 | Stamp(); 97 | }).toThrow(/Required: There must be requireMe in this stamp configuration/); 98 | 99 | Stamp = required({ 100 | staticProperties: { 101 | requireMe: required, 102 | }, 103 | }); 104 | expect(function() { 105 | Stamp(); 106 | }).toThrow(/Required: There must be requireMe in this stamp staticProperties/); 107 | }); 108 | 109 | it('should be composable to any stamp at any palce', function() { 110 | const Stamp = compose({ properties: { a: 1 } }) 111 | .compose(Required) 112 | .required({ 113 | methods: { 114 | requireMe: required, 115 | }, 116 | }); 117 | 118 | expect(function() { 119 | Stamp(); 120 | }).toThrow(/Required: There must be requireMe in this stamp methods/); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /packages/required/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.required = void 0; 7 | /* eslint @typescript-eslint/no-use-before-define: ["error", { "variables": false }] */ 8 | const compose_1 = __importDefault(require("@stamp/compose")); 9 | const core_1 = require("@stamp/core"); 10 | const is_1 = require("@stamp/is"); 11 | const { freeze } = Object; 12 | const { get, ownKeys } = Reflect; 13 | const required = function required(settings) { 14 | const localStamp = (this && 'compose' in this) ? this : Required; 15 | const { deepConfiguration } = localStamp.compose; 16 | const prevSettings = deepConfiguration === null || deepConfiguration === void 0 ? void 0 : deepConfiguration.Required; 17 | // filter out non stamp things 18 | const newSettings = (0, core_1.assign)({}, (0, compose_1.default)(prevSettings, settings).compose); 19 | return localStamp.compose({ deepConfiguration: { Required: newSettings } }); 20 | }; 21 | exports.required = required; 22 | freeze(exports.required); 23 | const checkDescriptorHaveThese = (descriptor, settings) => { 24 | if (descriptor && settings) { 25 | // Traverse settings and find if there is anything required. 26 | const settingsKeys = ownKeys(settings); 27 | for (const settingsKey of settingsKeys) { 28 | const settingsValue = get(settings, settingsKey); 29 | if ((0, is_1.isObject)(settingsValue)) { 30 | const metadataKeys = ownKeys(settingsValue); 31 | for (const metadataKey of metadataKeys) { 32 | const metadataValue = get(settingsValue, metadataKey); 33 | if (metadataValue === Required || metadataValue === exports.required) { 34 | // We found one thing which have to be provided. Let's check if it exists. 35 | const descValue = get(descriptor, settingsKey); 36 | if (!descValue || get(descValue, metadataKey) === undefined) { 37 | throw new Error(`Required: There must be ${String(metadataKey)} in this stamp ${String(settingsKey)}`); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | }; 45 | /** 46 | * TODO 47 | */ 48 | const Required = (0, compose_1.default)({ 49 | initializers: [ 50 | (_, opts) => { 51 | const descriptor = opts.stamp.compose; 52 | const { deepConfiguration } = descriptor; 53 | const settings = deepConfiguration === null || deepConfiguration === void 0 ? void 0 : deepConfiguration.Required; 54 | checkDescriptorHaveThese(descriptor, settings); 55 | }, 56 | ], 57 | staticProperties: { 58 | required: exports.required, 59 | }, 60 | }); 61 | exports.default = Required; 62 | // For CommonJS default export support 63 | module.exports = Required; 64 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: Required }); 65 | // Now is the time to freeze 66 | freeze(Required); 67 | -------------------------------------------------------------------------------- /packages/required/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint @typescript-eslint/no-use-before-define: ["error", { "variables": false }] */ 2 | import compose, { Composable, Descriptor, PropertyMap, Stamp } from '@stamp/compose'; 3 | import { assign } from '@stamp/core'; 4 | import { isObject } from '@stamp/is'; 5 | 6 | const { freeze } = Object; 7 | const { get, ownKeys } = Reflect; 8 | 9 | interface RequiredDescriptor extends Descriptor { 10 | deepConfiguration?: PropertyMap & { Required: Descriptor }; 11 | } 12 | 13 | interface StampMethodRequired { 14 | (this: Stamp, settings: Composable): Stamp; 15 | } 16 | export const required: StampMethodRequired = function required(settings): Stamp { 17 | const localStamp = (this && 'compose' in this) ? this : Required; 18 | const { deepConfiguration } = localStamp.compose as RequiredDescriptor; 19 | const prevSettings = deepConfiguration?.Required; 20 | 21 | // filter out non stamp things 22 | const newSettings = assign({}, compose(prevSettings, settings).compose); 23 | 24 | return localStamp.compose({ deepConfiguration: { Required: newSettings } }); 25 | }; 26 | 27 | freeze(required); 28 | 29 | interface CheckDescriptorHaveThese { 30 | (descriptor: Descriptor, settings: Descriptor | undefined): void; 31 | } 32 | const checkDescriptorHaveThese: CheckDescriptorHaveThese = (descriptor, settings) => { 33 | if (descriptor && settings) { 34 | // Traverse settings and find if there is anything required. 35 | const settingsKeys = ownKeys(settings); 36 | for (const settingsKey of settingsKeys) { 37 | const settingsValue = get(settings, settingsKey); 38 | if (isObject(settingsValue)) { 39 | const metadataKeys = ownKeys(settingsValue); 40 | for (const metadataKey of metadataKeys) { 41 | const metadataValue = get(settingsValue, metadataKey); 42 | if (metadataValue === Required || metadataValue === required) { 43 | // We found one thing which have to be provided. Let's check if it exists. 44 | const descValue = get(descriptor, settingsKey); 45 | if (!descValue || get(descValue, metadataKey) === undefined) { 46 | throw new Error(`Required: There must be ${String(metadataKey)} in this stamp ${String(settingsKey)}`); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | }; 54 | 55 | /** 56 | * TODO 57 | */ 58 | const Required = compose({ 59 | initializers: [ 60 | (_, opts): void => { 61 | const descriptor = opts.stamp.compose as RequiredDescriptor; 62 | const { deepConfiguration } = descriptor; 63 | const settings = deepConfiguration?.Required; 64 | checkDescriptorHaveThese(descriptor, settings); 65 | }, 66 | ], 67 | staticProperties: { 68 | required, 69 | }, 70 | }); 71 | 72 | export default Required; 73 | 74 | // For CommonJS default export support 75 | module.exports = Required; 76 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: Required }); 77 | 78 | // Now is the time to freeze 79 | freeze(Required); 80 | -------------------------------------------------------------------------------- /packages/required/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/required", 3 | "version": "1.0.1", 4 | "description": "Insist on a method/property/staticProperty/configuration presence", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/required" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2", 18 | "@stamp/core": "^1.0.1", 19 | "@stamp/is": "^1.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/required/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/shortcut/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | *.tsbuildinfo 4 | tsconfig.json 5 | -------------------------------------------------------------------------------- /packages/shortcut/README.md: -------------------------------------------------------------------------------- 1 | # @stamp/shortcut 2 | 3 | _Adds few handy static methods for simpler composition_ 4 | 5 | By composing the `@stamp/shortcut` into your stamp 6 | ```js 7 | MyStamp = MyStamp.compose(Shortcut); 8 | ``` 9 | you get few static methods. So, instead of typing 10 | ```js 11 | MyStamp.compose({ deepProperties: bla }); 12 | ``` 13 | you will be able to write 14 | ```js 15 | MyStamp.deepProps(bla); 16 | 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | import Shortcut from '@stamp/shortcut'; 23 | 24 | const MyStamp = Shortcut 25 | .methods({ myMethod() {} }) 26 | .props({ foo: 'foo' }) 27 | .deepProps({ deepFoo: { foo: 'foo 2' } }) 28 | .statics({ bar: 'bar' }) 29 | .deepStatics({ deepBar: { bar: 'bar 2' } }) 30 | .conf({ something: 'something' }) 31 | .deepConf({ deepSomething: { something: 'something' } }) 32 | .init((arg, {stamp, args, instance}) => { /* initializer */ }) 33 | .composers(({stamp, composables}) => { /* composer */ }); 34 | ``` 35 | 36 | Or you can import each individual shortcut function: 37 | ```js 38 | import { 39 | methods, props, deepProps, statics, deepStatics, conf, deepConf, init, composers 40 | } from '@stamp/shortcut'; 41 | 42 | const Stamp1 = methods({ 43 | method1() { } 44 | }); 45 | const Stamp2 = props({ 46 | prop2: 2 47 | }); 48 | const Stamp3 = init(function () { 49 | console.log(3); 50 | }); 51 | // etc 52 | ``` 53 | 54 | In this case the functions will not add any static shortcut methods to your stamps, meaning that the following will throw: 55 | ```js 56 | methods({ method1() {} }).props(); // Error: undefined "props" is not a function 57 | init(function () {}).init(); // Error: undefined "init" is not a function 58 | // etc 59 | ``` 60 | 61 | 62 | ## API 63 | 64 | ### Static methods 65 | 66 | #### methods 67 | `stamp.methods(Object) -> Stamp` 68 | 69 | #### props 70 | `stamp.props(Object) -> Stamp` 71 | 72 | #### deepProps 73 | `stamp.deepProps(Object) -> Stamp` 74 | 75 | #### statics 76 | `stamp.statics(Object) -> Stamp` 77 | 78 | #### deepStatics 79 | `stamp.deepStatics(Object) -> Stamp` 80 | 81 | #### conf 82 | `stamp.conf(Object) -> Stamp` 83 | 84 | #### deepConf 85 | `stamp.deepConf(Object) -> Stamp` 86 | 87 | #### init 88 | `stamp.init(...Function) -> Stamp` 89 | 90 | #### composers 91 | `stamp.composers(...Function) -> Stamp` 92 | -------------------------------------------------------------------------------- /packages/shortcut/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const compose_1 = __importDefault(require("@stamp/compose")); 7 | const createShortcut = (propName) => { 8 | // eslint-disable-next-line func-names 9 | return function (arg) { 10 | const param = { [propName]: arg }; 11 | return (this === null || this === void 0 ? void 0 : this.compose) ? this.compose(param) : (0, compose_1.default)(param); 12 | }; 13 | }; 14 | const properties = createShortcut('properties'); 15 | const staticProperties = createShortcut('staticProperties'); 16 | const configuration = createShortcut('configuration'); 17 | const deepProperties = createShortcut('deepProperties'); 18 | const staticDeepProperties = createShortcut('staticDeepProperties'); 19 | const deepConfiguration = createShortcut('deepConfiguration'); 20 | const initializers = createShortcut('initializers'); 21 | // TODO: Stamp's `ComposeMethod` should issue StampWithShortcuts 22 | // TODO: Augment `Stamp` signature with `StampWithShortcuts` 23 | /** 24 | * TODO 25 | */ 26 | const Shortcut = (0, compose_1.default)({ 27 | staticProperties: { 28 | methods: createShortcut('methods'), 29 | props: properties, 30 | properties, 31 | statics: staticProperties, 32 | staticProperties, 33 | conf: configuration, 34 | configuration, 35 | deepProps: deepProperties, 36 | deepProperties, 37 | deepStatics: staticDeepProperties, 38 | staticDeepProperties, 39 | deepConf: deepConfiguration, 40 | deepConfiguration, 41 | init: initializers, 42 | initializers, 43 | composers: createShortcut('composers'), 44 | propertyDescriptors: createShortcut('propertyDescriptors'), 45 | staticPropertyDescriptors: createShortcut('staticPropertyDescriptors'), 46 | }, 47 | }); 48 | exports.default = Shortcut; 49 | // For CommonJS default export support 50 | module.exports = Shortcut; 51 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: Shortcut }); 52 | -------------------------------------------------------------------------------- /packages/shortcut/index.ts: -------------------------------------------------------------------------------- 1 | import compose, { Descriptor, Stamp } from '@stamp/compose'; 2 | 3 | interface ShortcutMethod { 4 | (this: StampWithShortcuts, arg: unknown): StampWithShortcuts; 5 | } 6 | 7 | interface CreateShortcut { 8 | (propName: string): ShortcutMethod; 9 | } 10 | 11 | const createShortcut: CreateShortcut = (propName) => { 12 | // eslint-disable-next-line func-names 13 | return function(arg) { 14 | const param = { [propName]: arg }; 15 | return this?.compose ? this.compose(param) : compose(param); 16 | } as ShortcutMethod; 17 | }; 18 | 19 | const properties = createShortcut('properties'); 20 | const staticProperties = createShortcut('staticProperties'); 21 | const configuration = createShortcut('configuration'); 22 | const deepProperties = createShortcut('deepProperties'); 23 | const staticDeepProperties = createShortcut('staticDeepProperties'); 24 | const deepConfiguration = createShortcut('deepConfiguration'); 25 | const initializers = createShortcut('initializers'); 26 | 27 | // TODO: enhance typing 28 | export type Composable = Stamp | Descriptor; 29 | 30 | interface ComposeMethod { 31 | (/* this: unknown, */ ...args: (Composable | undefined)[]): StampWithShortcuts; 32 | } 33 | 34 | // TODO: enhance typing 35 | export type ComposeProperty = ComposeMethod & Descriptor; 36 | 37 | /** 38 | *TODO 39 | * 40 | * @export 41 | * @interface StampWithShortcuts 42 | * @extends {Stamp} 43 | */ 44 | export interface StampWithShortcuts extends Stamp { 45 | methods: ShortcutMethod; 46 | props: ShortcutMethod; 47 | properties: ShortcutMethod; 48 | statics: ShortcutMethod; 49 | staticProperties: ShortcutMethod; 50 | conf: ShortcutMethod; 51 | configuration: ShortcutMethod; 52 | deepProps: ShortcutMethod; 53 | deepProperties: ShortcutMethod; 54 | deepStatics: ShortcutMethod; 55 | staticDeepProperties: ShortcutMethod; 56 | deepConf: ShortcutMethod; 57 | deepConfiguration: ShortcutMethod; 58 | init: ShortcutMethod; 59 | initializers: ShortcutMethod; 60 | composers: ShortcutMethod; 61 | propertyDescriptors: ShortcutMethod; 62 | staticPropertyDescriptors: ShortcutMethod; 63 | compose: ComposeProperty; 64 | } 65 | 66 | // TODO: Stamp's `ComposeMethod` should issue StampWithShortcuts 67 | // TODO: Augment `Stamp` signature with `StampWithShortcuts` 68 | /** 69 | * TODO 70 | */ 71 | const Shortcut = compose({ 72 | staticProperties: { 73 | methods: createShortcut('methods'), 74 | props: properties, 75 | properties, 76 | statics: staticProperties, 77 | staticProperties, 78 | conf: configuration, 79 | configuration, 80 | deepProps: deepProperties, 81 | deepProperties, 82 | deepStatics: staticDeepProperties, 83 | staticDeepProperties, 84 | deepConf: deepConfiguration, 85 | deepConfiguration, 86 | init: initializers, 87 | initializers, 88 | composers: createShortcut('composers'), 89 | propertyDescriptors: createShortcut('propertyDescriptors'), 90 | staticPropertyDescriptors: createShortcut('staticPropertyDescriptors'), 91 | }, 92 | }) as StampWithShortcuts; 93 | 94 | export default Shortcut; 95 | 96 | // For CommonJS default export support 97 | module.exports = Shortcut; 98 | Object.defineProperty(module.exports, 'default', { enumerable: false, value: Shortcut }); 99 | -------------------------------------------------------------------------------- /packages/shortcut/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stamp/shortcut", 3 | "version": "1.0.2", 4 | "description": "Adds handy shortcuts for stamp composition", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/stampit-org/stamp/tree/master/packages/shortcut" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "tsc", 14 | "link": "npm run build && lerna link" 15 | }, 16 | "dependencies": { 17 | "@stamp/compose": "^1.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/shortcut/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // "compileOnSave": true, 3 | "compilerOptions": { 4 | "incremental": true, 5 | "strict": true, 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "alwaysStrict": true, 9 | "strictBindCallApply": true, 10 | "strictNullChecks": true, 11 | "strictFunctionTypes": true, 12 | "strictPropertyInitialization": true, 13 | 14 | "allowSyntheticDefaultImports": true, 15 | "esModuleInterop": true, 16 | 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitReturns": true, 19 | "noUnusedLocals": true, 20 | // "noUnusedParameters": false, 21 | // "suppressExcessPropertyErrors": false, 22 | // "suppressImplicitAnyIndexErrors": false, 23 | 24 | "target": "es2017", 25 | "module": "commonjs", 26 | "moduleResolution": "node", 27 | "newLine": "LF", 28 | "noEmitOnError": true, 29 | "declaration": true, 30 | // "noImplicitAny": true, 31 | // "lib": ["dom", "es2018"], 32 | "lib": ["es2018"], 33 | // "outDir": "dist", 34 | // "rootDir": "src", 35 | // "typeRoots": ["src/types", "node_modules/@types"], 36 | 37 | "stripInternal": false, 38 | 39 | // "experimentalDecorators": true, 40 | "pretty": true 41 | }, 42 | "exclude": [ 43 | "node_modules", 44 | "**/__tests__/*", 45 | ] 46 | } 47 | --------------------------------------------------------------------------------