1
0
Fork 0

Adding upstream version 1.2.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-04-22 16:41:43 +02:00
parent eac40c2ddc
commit e864a6175d
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
14 changed files with 584 additions and 0 deletions

7
.babelrc Normal file
View file

@ -0,0 +1,7 @@
{
"modules": "umd",
"loose": "all",
"compact": true,
"comments": false,
"stage": 0
}

50
.eslintrc Normal file
View file

@ -0,0 +1,50 @@
{
"parser": "babel-eslint",
"extends": "eslint:recommended",
"env": {
"browser": true
},
"ecmaFeatures": {
"modules": true,
"jsx": true
},
"rules": {
"no-unused-vars": [1, { "args": "after-used" }],
"no-cond-assign": 1,
"semi": 2,
"camelcase": 0,
"comma-style": 2,
"comma-dangle": [2, "never"],
"indent": [2, "tab", {"SwitchCase": 1}],
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
"no-trailing-spaces": [2, { "skipBlankLines": true }],
"max-nested-callbacks": [2, 3],
"no-eval": 2,
"no-implied-eval": 2,
"no-new-func": 2,
"guard-for-in": 2,
"eqeqeq": 2,
"no-else-return": 2,
"no-redeclare": 2,
"no-dupe-keys": 2,
"radix": 2,
"strict": [2, "never"],
"no-shadow": 0,
"callback-return": [1, ["callback", "cb", "next", "done"]],
"no-delete-var": 2,
"no-undef-init": 2,
"no-shadow-restricted-names": 2,
"handle-callback-err": 0,
"no-lonely-if": 2,
"space-return-throw-case": 2,
"constructor-super": 2,
"no-this-before-super": 2,
"no-dupe-class-members": 2,
"no-const-assign": 2,
"prefer-spread": 2,
"no-useless-concat": 2,
"no-var": 2,
"object-shorthand": 2,
"prefer-arrow-callback": 2
}
}

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
npm-debug.log
node_modules
dist
.DS_Store

1
.travis.yml Normal file
View file

@ -0,0 +1 @@
language: node_js

20
LICENSE Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 Jason Miller
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

120
README.md Normal file
View file

@ -0,0 +1,120 @@
# decko [![NPM Version](https://img.shields.io/npm/v/decko.svg?style=flat)](https://npmjs.com/package/decko) [![Build Status](https://travis-ci.org/developit/decko.svg?branch=master)](https://travis-ci.org/developit/decko)
A concise implementation of the three most useful [decorators](https://github.com/wycats/javascript-decorators):
- `@bind`: make the value of `this` constant within a method
- `@debounce`: throttle calls to a method
- `@memoize`: cache return values based on arguments
Decorators help simplify code by replacing the noise of common patterns with declarative annotations.
Conversely, decorators can also be overused and create obscurity.
Decko establishes 3 standard decorators that are immediately recognizable, so you can avoid creating decorators in your own codebase.
> 💡 **Tip:** decko is particularly well-suited to [**Preact Classful Components**](https://github.com/developit/preact).
>
> 💫 **Note:**
> - For Babel 6+, be sure to install [babel-plugin-transform-decorators-legacy](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy).
> - For Typescript, be sure to enable `{"experimentalDecorators": true}` in your tsconfig.json.
## Installation
Available on [npm](https://npmjs.com/package/decko):
```sh
npm i -S decko
```
## Usage
Each decorator method is available as a named import.
```js
import { bind, memoize, debounce } from 'decko';
```
### `@bind`
```js
class Example {
@bind
foo() {
// the value of `this` is always the object from which foo() was referenced.
return this;
}
}
let e = new Example();
assert.equal(e.foo.call(null), e);
```
### `@memoize`
> Cache values returned from the decorated function.
> Uses the first argument as a cache key.
> _Cache keys are always converted to strings._
>
> ##### Options:
>
> `caseSensitive: false` - _Makes cache keys case-insensitive_
>
> `cache: {}` - _Presupply cache storage, for seeding or sharing entries_
```js
class Example {
@memoize
expensive(key) {
let start = Date.now();
while (Date.now()-start < 500) key++;
return key;
}
}
let e = new Example();
// this takes 500ms
let one = e.expensive(1);
// this takes 0ms
let two = e.expensive(1);
// this takes 500ms
let three = e.expensive(2);
```
### `@debounce`
> Throttle calls to the decorated function. To debounce means "call this at most once per N ms".
> All outward function calls get collated into a single inward call, and only the latest (most recent) arguments as passed on to the debounced function.
>
> ##### Options:
>
> `delay: 0` - _The number of milliseconds to buffer calls for._
```js
class Example {
@debounce
foo() {
return this;
}
}
let e = new Example();
// this will only call foo() once:
for (let i=1000; i--) e.foo();
```
---
License
-------
MIT

33
package.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "decko",
"version": "1.2.0",
"main": "dist/decko.js",
"types": "dist/decko.d.ts",
"description": "A collection of the most useful property decorators.",
"scripts": {
"build": "mkdir -p dist && babel -f src/decko.js -s -o $npm_package_main < src/decko.js && npm run build:ts",
"build:ts": "cp src/decko.d.ts dist/",
"test": "npm run test:ts && eslint {src,tests}/**.js && mocha --compilers js:babel/register tests/**/*.js",
"test:ts": "tsc -p ./",
"style:ts": "tsfmt -r",
"prepublish": "npm run build",
"release": "npm run build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"files": [
"src",
"dist"
],
"repository": {
"type": "git",
"url": "git://github.com/developit/decko.git"
},
"devDependencies": {
"babel": "^5.8.21",
"babel-eslint": "^4.1.6",
"chai": "^3.2.0",
"eslint": "^1.10.3",
"mocha": "^2.3.0",
"typescript": "2.1.6",
"typescript-formatter": "4.1.1"
}
}

30
src/decko.d.ts vendored Normal file
View file

@ -0,0 +1,30 @@
/**
*
*/
export function bind<T>(
target: Object,
propertyKey: string | symbol,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void;
export function bind(): MethodDecorator;
/**
* @param caseSensitive Makes cache keys case-insensitive
* @param cache Presupply cache storage, for seeding or sharing entries
*/
export function memoize<T>(
target: Object,
propertyKey: string | symbol,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void;
export function memoize(caseSensitive?: boolean, cache?: Object): MethodDecorator;
/**
* @param delay number
*/
export function debounce<T>(
target: Object,
propertyKey: string | symbol,
descriptor?: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> | void;
export function debounce(delay?: number): MethodDecorator;

98
src/decko.js Normal file
View file

@ -0,0 +1,98 @@
const EMPTY = {};
const HOP = Object.prototype.hasOwnProperty;
let fns = {
/** let cachedFn = memoize(originalFn); */
memoize(fn, opt=EMPTY) {
let cache = opt.cache || {};
return function(...a) {
let k = String(a[0]);
if (opt.caseSensitive===false) k = k.toLowerCase();
return HOP.call(cache,k) ? cache[k] : (cache[k] = fn.apply(this, a));
};
},
/** let throttled = debounce(10, console.log); */
debounce(fn, opts) {
if (typeof opts==='function') { let p = fn; fn = opts; opts = p; }
let delay = opts && opts.delay || opts || 0,
args, context, timer;
return function(...a) {
args = a;
context = this;
if (!timer) timer = setTimeout( () => {
fn.apply(context, args);
args = context = timer = null;
}, delay);
};
},
bind(target, key, { value: fn }) {
return {
configurable: true,
get() {
let value = fn.bind(this);
Object.defineProperty(this, key, {
value,
configurable: true,
writable: true
});
return value;
}
};
}
};
let memoize = multiMethod(fns.memoize),
debounce = multiMethod(fns.debounce),
bind = multiMethod((f,c)=>f.bind(c), ()=>fns.bind);
export { memoize, debounce, bind };
export default { memoize, debounce, bind };
/** Creates a function that supports the following calling styles:
* d() - returns an unconfigured decorator
* d(opts) - returns a configured decorator
* d(fn, opts) - returns a decorated proxy to `fn`
* d(target, key, desc) - the decorator itself
*
* @Example:
* // simple identity deco:
* let d = multiMethod( fn => fn );
*
* class Foo {
* @d
* bar() { }
*
* @d()
* baz() { }
*
* @d({ opts })
* bat() { }
*
* bap = d(() => {})
* }
*/
function multiMethod(inner, deco) {
deco = deco || inner.decorate || decorator(inner);
let d = deco();
return (...args) => {
let l = args.length;
return (l<2 ? deco : (l>2 ? d : inner))(...args);
};
}
/** Returns function supports the forms:
* deco(target, key, desc) -> decorate a method
* deco(Fn) -> call the decorator proxy on a function
*/
function decorator(fn) {
return opt => (
typeof opt==='function' ? fn(opt) : (target, key, desc) => {
desc.value = fn(desc.value, opt, target, key, desc);
}
);
}

37
tests/bind.js Normal file
View file

@ -0,0 +1,37 @@
import { bind } from '..';
import { expect } from 'chai';
/*global describe,it*/
describe('bind()', () => {
it('should bind when used as a simple decorator', next => {
let c = {
@bind
foo() {
return this;
}
};
expect(c.foo()).to.equal(c);
let p = c.foo;
expect(p()).to.equal(c);
let a = {};
expect(c.foo.call(a)).to.equal(c);
next();
});
it('should bind when used as a function', next => {
let ctx = {},
c = bind(function(){ return this; }, ctx);
expect(c()).to.equal(ctx);
let a = {};
expect(c.call(a)).to.equal(ctx);
next();
});
});

71
tests/debounce.js Normal file
View file

@ -0,0 +1,71 @@
import { debounce } from '..';
import { expect } from 'chai';
/*global describe,it*/
describe('debounce()', () => {
it('should debounce when used as a simple decorator', next => {
let c = {
calls: 0,
args: null,
@debounce
foo(...args) {
c.calls++;
c.args = args;
c.context = this;
}
};
expect(c).to.have.property('calls', 0);
c.foo(1);
expect(c).to.have.property('calls', 0);
c.foo(2);
c.foo(3);
setTimeout( () => {
expect(c).to.have.property('calls', 1);
expect(c.args).to.deep.equal([3]);
expect(c.context).to.equal(c);
next();
}, 20);
});
it('should debounce when used as a function', next => {
let c = debounce( (...args) => {
m.calls++;
m.args = args;
}),
m = { calls:0, args:null };
expect(m).to.have.property('calls', 0);
c(1);
expect(m).to.have.property('calls', 0);
c(2);
c(3);
setTimeout( () => {
expect(m).to.have.property('calls', 1);
expect(m.args).to.deep.equal([3]);
next();
}, 20);
});
it('should support passing a delay', next => {
let c = debounce(5, (...args) => {
m.calls.push(args);
}),
m = { calls:[] };
c(1);
setTimeout(()=> c(2), 1);
setTimeout(()=> c(3), 10);
setTimeout(()=> c(4), 14);
setTimeout(()=> c(5), 22);
expect(m.calls).to.have.length(0);
setTimeout( () => {
expect(m.calls).to.deep.equal([ [2], [4], [5] ]);
next();
}, 30);
});
});

17
tests/index.ts Normal file
View file

@ -0,0 +1,17 @@
import { bind, debounce, memoize } from '..';
class C {
@bind
foo() { }
@debounce
moo() { }
@debounce(1000)
mooWithCustomDelay() { }
@memoize
mem() { }
@memoize(true)
memWithConfig() { }
}

72
tests/memoize.js Normal file
View file

@ -0,0 +1,72 @@
import { memoize } from '..';
import { expect } from 'chai';
/*global describe,it*/
describe('memoize()', () => {
it('should memoize when used as a simple decorator', next => {
let c = {
@memoize
foo(key) {
c[key] = (c[key] || 0) + 1;
}
};
expect(c).not.to.have.property('a');
c.foo('a');
expect(c).to.have.property('a', 1);
c.foo('a');
c.foo('a');
expect(c).to.have.property('a', 1);
next();
});
it('should memoize when used as a function', next => {
let c = memoize( key => {
m[key] = (m[key] || 0) + 1;
}),
m = {};
expect(m).not.to.have.property('a');
c('a');
expect(m).to.have.property('a', 1);
c('a');
c('a');
expect(m).to.have.property('a', 1);
next();
});
it('should memoize when called without arguments', next => {
let c = memoize( key => {
m[key] = (m[key] || 0) + 1;
}),
m = {};
expect(m).not.to.have.property('undefined');
c();
expect(m).to.have.property('undefined', 1);
c();
c();
expect(m).to.have.property('undefined', 1);
next();
});
it('should memoize when called with an empty string', next => {
let c = memoize( key => {
m[key] = (m[key] || 0) + 1;
}),
m = {};
expect(m).not.to.have.property('');
c('');
expect(m).to.have.property('', 1);
c('');
c('');
expect(m).to.have.property('', 1);
next();
});
});

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "es2015",
"target": "es2016",
"lib": [
"dom",
"es2016"
],
"baseUrl": "./",
"noImplicitAny": true,
"experimentalDecorators": true,
"sourceMap": false,
"moduleResolution": "node",
"strictNullChecks": true,
"declaration": true,
"noEmit": true,
"pretty": true,
"outDir": "ts-output"
},
"include": [
"src/decko.d.ts",
"tests/index.ts"
]
}