@unshared/decorators 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Stanley Horwood <stanley@hsjm.io>
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.
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var debounce = require("@unshared/functions/debounce");
3
+ function Debounce(delay) {
4
+ return (target, propertyName, descriptor) => {
5
+ const method = descriptor.value;
6
+ return descriptor.value = debounce.debounce(method, delay), descriptor;
7
+ };
8
+ }
9
+ exports.Debounce = Debounce;
10
+ //# sourceMappingURL=Debounce.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Debounce.cjs","sources":["../Debounce.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { debounce } from '@unshared/functions/debounce'\n\n/**\n * Debounce a method so that it will only execute after the specified delay. If the method\n * is called multiple times before the delay has passed, the method will only execute once\n * after the delay has passed. The method will be called with the parameters of the last call.\n *\n * **Note:** This decorator will omit the return value of the method and return `undefined`.\n *\n * @param delay The delay in milliseconds to wait before executing the method.\n * @returns The method descriptor.\n * @example\n * // Declare a class with a debounced method.\n * class Greeter {\n * ->@Debounce(100)\n * greet(name: string) { return `Hello, ${name}! - ${Date.now()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice')\n * instance.greet('Bob')\n * instance.greet('Charlie')\n *\n * // After 100ms the method will be called with the parameters of the last call.\n * // => Hello, Charlie!\n */\nexport function Debounce<T extends Function<void>>(delay: number): MethodDecorator<T> {\n return (target, propertyName, descriptor) => {\n const method = descriptor.value!\n descriptor.value = debounce(method, delay) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n beforeAll(() => {\n vi.useFakeTimers()\n })\n\n test('should not call the function if the delay has not passed', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n instance.fn()\n instance.fn()\n vi.advanceTimersByTime(50)\n expect(fn).not.toHaveBeenCalled()\n })\n\n test('should call the function once after the delay has passed', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(10) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n instance.fn()\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(1)\n })\n\n test('should call the function once with the parameters of the last call', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(10) fn(name: string) { fn(name) } }\n const instance = new Greeter()\n instance.fn('Alice')\n instance.fn('Bob')\n instance.fn('Charlie')\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledWith('Charlie')\n })\n\n test('should call the function multiple times if the delay has passed', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(10) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n vi.advanceTimersByTime(100)\n instance.fn()\n vi.advanceTimersByTime(100)\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(3)\n })\n\n test('should return undefined', () => {\n const fn = vi.fn(() => 'foobar')\n class Greeter { @Debounce(10) fn() { return fn() } }\n const instance = new Greeter()\n const result = instance.fn()\n expect(result).toBeUndefined()\n })\n}\n"],"names":["debounce"],"mappings":";;AA4BO,SAAS,SAAmC,OAAmC;AAC7E,SAAA,CAAC,QAAQ,cAAc,eAAe;AAC3C,UAAM,SAAS,WAAW;AAC1B,WAAA,WAAW,QAAQA,SAAAA,SAAS,QAAQ,KAAK,GAClC;AAAA,EAAA;AAEX;;"}
@@ -0,0 +1,30 @@
1
+ import { Function, MethodDecorator } from '@unshared/types';
2
+
3
+ /**
4
+ * Debounce a method so that it will only execute after the specified delay. If the method
5
+ * is called multiple times before the delay has passed, the method will only execute once
6
+ * after the delay has passed. The method will be called with the parameters of the last call.
7
+ *
8
+ * **Note:** This decorator will omit the return value of the method and return `undefined`.
9
+ *
10
+ * @param delay The delay in milliseconds to wait before executing the method.
11
+ * @returns The method descriptor.
12
+ * @example
13
+ * // Declare a class with a debounced method.
14
+ * class Greeter {
15
+ * ->@Debounce(100)
16
+ * greet(name: string) { return `Hello, ${name}! - ${Date.now()}` }
17
+ * }
18
+ *
19
+ * // The first call to the method will be executed.
20
+ * const instance = new Greeter()
21
+ * instance.greet('Alice')
22
+ * instance.greet('Bob')
23
+ * instance.greet('Charlie')
24
+ *
25
+ * // After 100ms the method will be called with the parameters of the last call.
26
+ * // => Hello, Charlie!
27
+ */
28
+ declare function Debounce<T extends Function<void>>(delay: number): MethodDecorator<T>;
29
+
30
+ export { Debounce };
@@ -0,0 +1,11 @@
1
+ import { debounce } from "@unshared/functions/debounce";
2
+ function Debounce(delay) {
3
+ return (target, propertyName, descriptor) => {
4
+ const method = descriptor.value;
5
+ return descriptor.value = debounce(method, delay), descriptor;
6
+ };
7
+ }
8
+ export {
9
+ Debounce
10
+ };
11
+ //# sourceMappingURL=Debounce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Debounce.js","sources":["../Debounce.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { debounce } from '@unshared/functions/debounce'\n\n/**\n * Debounce a method so that it will only execute after the specified delay. If the method\n * is called multiple times before the delay has passed, the method will only execute once\n * after the delay has passed. The method will be called with the parameters of the last call.\n *\n * **Note:** This decorator will omit the return value of the method and return `undefined`.\n *\n * @param delay The delay in milliseconds to wait before executing the method.\n * @returns The method descriptor.\n * @example\n * // Declare a class with a debounced method.\n * class Greeter {\n * ->@Debounce(100)\n * greet(name: string) { return `Hello, ${name}! - ${Date.now()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice')\n * instance.greet('Bob')\n * instance.greet('Charlie')\n *\n * // After 100ms the method will be called with the parameters of the last call.\n * // => Hello, Charlie!\n */\nexport function Debounce<T extends Function<void>>(delay: number): MethodDecorator<T> {\n return (target, propertyName, descriptor) => {\n const method = descriptor.value!\n descriptor.value = debounce(method, delay) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n beforeAll(() => {\n vi.useFakeTimers()\n })\n\n test('should not call the function if the delay has not passed', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n instance.fn()\n instance.fn()\n vi.advanceTimersByTime(50)\n expect(fn).not.toHaveBeenCalled()\n })\n\n test('should call the function once after the delay has passed', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(10) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n instance.fn()\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(1)\n })\n\n test('should call the function once with the parameters of the last call', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(10) fn(name: string) { fn(name) } }\n const instance = new Greeter()\n instance.fn('Alice')\n instance.fn('Bob')\n instance.fn('Charlie')\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledWith('Charlie')\n })\n\n test('should call the function multiple times if the delay has passed', () => {\n const fn = vi.fn()\n class Greeter { @Debounce(10) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n vi.advanceTimersByTime(100)\n instance.fn()\n vi.advanceTimersByTime(100)\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(3)\n })\n\n test('should return undefined', () => {\n const fn = vi.fn(() => 'foobar')\n class Greeter { @Debounce(10) fn() { return fn() } }\n const instance = new Greeter()\n const result = instance.fn()\n expect(result).toBeUndefined()\n })\n}\n"],"names":[],"mappings":";AA4BO,SAAS,SAAmC,OAAmC;AAC7E,SAAA,CAAC,QAAQ,cAAc,eAAe;AAC3C,UAAM,SAAS,WAAW;AAC1B,WAAA,WAAW,QAAQ,SAAS,QAAQ,KAAK,GAClC;AAAA,EAAA;AAEX;"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var memoize = require("@unshared/functions/memoize");
3
+ function Memoize(options) {
4
+ return function(_target, _propertyName, descriptor) {
5
+ const method = descriptor.value;
6
+ return descriptor.value = memoize.memoize(method, options), descriptor;
7
+ };
8
+ }
9
+ exports.Memoize = Memoize;
10
+ //# sourceMappingURL=Memoize.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Memoize.cjs","sources":["../Memoize.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { MemoizeOptions, memoize } from '@unshared/functions/memoize'\n\n/**\n * Decorate a method to memoize it's result based on the arguments. Meaning\n * that it will return the same result without executing the method again,\n * **unless the arguments change**.\n *\n * @param options The memoization options.\n * @returns The method descriptor.\n * @example\n * // Declare a class with a memoized method.\n * class Greeter {\n * ->@Memoize()\n * greet(name: string) { return `Hello, ${name}! - ${Math.random()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'\n * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'\n * instance.greet('Bob') // 'Hello, Bob! - 0.987654321'\n */\nexport function Memoize<T extends Function>(options?: MemoizeOptions<T>): MethodDecorator<T> {\n return function(_target, _propertyName, descriptor) {\n const method = descriptor.value!\n descriptor.value = memoize(method, options) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n test('should memoize the method', () => {\n const fn = vi.fn(Math.random)\n class MyClass { @Memoize() getId() { return fn() } }\n const instance = new MyClass()\n const id1 = instance.getId()\n const id2 = instance.getId()\n expect(id1).toStrictEqual(id2)\n expect(fn).toHaveBeenCalledTimes(1)\n expect(fn).toHaveBeenCalledWith()\n })\n\n test('should memoize the method by parameter', () => {\n const fn = vi.fn((n = 0) => (n as number) + Math.random())\n class MyClass { @Memoize() getId(n: number) { return fn(n) } }\n const instance = new MyClass()\n const id1 = instance.getId(1)\n const id2 = instance.getId(1)\n const id3 = instance.getId(2)\n expect(id1).toStrictEqual(id2)\n expect(id1).not.toStrictEqual(id3)\n expect(fn).toHaveBeenCalledTimes(2)\n expect(fn).toHaveBeenCalledWith(1)\n expect(fn).toHaveBeenCalledWith(2)\n })\n\n test('should preserve the method context', () => {\n class MyClass { value = { foo: 42 }; @Memoize() getValue() { return this?.value } }\n const instance = new MyClass()\n const result1 = instance.getValue()\n const result2 = instance.value\n expect(result1).toBe(result2)\n })\n}\n"],"names":["memoize"],"mappings":";;AAuBO,SAAS,QAA4B,SAAiD;AACpF,SAAA,SAAS,SAAS,eAAe,YAAY;AAClD,UAAM,SAAS,WAAW;AAC1B,WAAA,WAAW,QAAQA,QAAAA,QAAQ,QAAQ,OAAO,GACnC;AAAA,EAAA;AAEX;;"}
@@ -0,0 +1,26 @@
1
+ import { Function, MethodDecorator } from '@unshared/types';
2
+ import { MemoizeOptions } from '@unshared/functions/memoize';
3
+
4
+ /**
5
+ * Decorate a method to memoize it's result based on the arguments. Meaning
6
+ * that it will return the same result without executing the method again,
7
+ * **unless the arguments change**.
8
+ *
9
+ * @param options The memoization options.
10
+ * @returns The method descriptor.
11
+ * @example
12
+ * // Declare a class with a memoized method.
13
+ * class Greeter {
14
+ * ->@Memoize()
15
+ * greet(name: string) { return `Hello, ${name}! - ${Math.random()}` }
16
+ * }
17
+ *
18
+ * // The first call to the method will be executed.
19
+ * const instance = new Greeter()
20
+ * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'
21
+ * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'
22
+ * instance.greet('Bob') // 'Hello, Bob! - 0.987654321'
23
+ */
24
+ declare function Memoize<T extends Function>(options?: MemoizeOptions<T>): MethodDecorator<T>;
25
+
26
+ export { Memoize };
@@ -0,0 +1,11 @@
1
+ import { memoize } from "@unshared/functions/memoize";
2
+ function Memoize(options) {
3
+ return function(_target, _propertyName, descriptor) {
4
+ const method = descriptor.value;
5
+ return descriptor.value = memoize(method, options), descriptor;
6
+ };
7
+ }
8
+ export {
9
+ Memoize
10
+ };
11
+ //# sourceMappingURL=Memoize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Memoize.js","sources":["../Memoize.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { MemoizeOptions, memoize } from '@unshared/functions/memoize'\n\n/**\n * Decorate a method to memoize it's result based on the arguments. Meaning\n * that it will return the same result without executing the method again,\n * **unless the arguments change**.\n *\n * @param options The memoization options.\n * @returns The method descriptor.\n * @example\n * // Declare a class with a memoized method.\n * class Greeter {\n * ->@Memoize()\n * greet(name: string) { return `Hello, ${name}! - ${Math.random()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'\n * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'\n * instance.greet('Bob') // 'Hello, Bob! - 0.987654321'\n */\nexport function Memoize<T extends Function>(options?: MemoizeOptions<T>): MethodDecorator<T> {\n return function(_target, _propertyName, descriptor) {\n const method = descriptor.value!\n descriptor.value = memoize(method, options) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n test('should memoize the method', () => {\n const fn = vi.fn(Math.random)\n class MyClass { @Memoize() getId() { return fn() } }\n const instance = new MyClass()\n const id1 = instance.getId()\n const id2 = instance.getId()\n expect(id1).toStrictEqual(id2)\n expect(fn).toHaveBeenCalledTimes(1)\n expect(fn).toHaveBeenCalledWith()\n })\n\n test('should memoize the method by parameter', () => {\n const fn = vi.fn((n = 0) => (n as number) + Math.random())\n class MyClass { @Memoize() getId(n: number) { return fn(n) } }\n const instance = new MyClass()\n const id1 = instance.getId(1)\n const id2 = instance.getId(1)\n const id3 = instance.getId(2)\n expect(id1).toStrictEqual(id2)\n expect(id1).not.toStrictEqual(id3)\n expect(fn).toHaveBeenCalledTimes(2)\n expect(fn).toHaveBeenCalledWith(1)\n expect(fn).toHaveBeenCalledWith(2)\n })\n\n test('should preserve the method context', () => {\n class MyClass { value = { foo: 42 }; @Memoize() getValue() { return this?.value } }\n const instance = new MyClass()\n const result1 = instance.getValue()\n const result2 = instance.value\n expect(result1).toBe(result2)\n })\n}\n"],"names":[],"mappings":";AAuBO,SAAS,QAA4B,SAAiD;AACpF,SAAA,SAAS,SAAS,eAAe,YAAY;AAClD,UAAM,SAAS,WAAW;AAC1B,WAAA,WAAW,QAAQ,QAAQ,QAAQ,OAAO,GACnC;AAAA,EAAA;AAEX;"}
package/dist/Once.cjs ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var once = require("@unshared/functions/once");
3
+ function Once() {
4
+ return (target, propertyName, descriptor) => {
5
+ const method = descriptor.value;
6
+ return descriptor.value = once.once(method), descriptor;
7
+ };
8
+ }
9
+ exports.Once = Once;
10
+ //# sourceMappingURL=Once.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Once.cjs","sources":["../Once.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { once } from '@unshared/functions/once'\n\n/**\n * Decorate a method to memoize it's result. Meaning that if the method is called,\n * it will return the same result without executing the method again, **even if the\n * method is called with different arguments**.\n *\n * @returns The method descriptor.\n * @example\n * // Declare a class with a onced method.\n * class Greeter {\n * ->@Once()\n * greet(name: string) { return `Hello, ${name}! - ${Math.random()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'\n * instance.greet('Bob') // 'Hello, Alice! - 0.123456789'\n */\nexport function Once<T extends Function>(): MethodDecorator<T> {\n return (target, propertyName, descriptor) => {\n const method = descriptor.value!\n descriptor.value = once(method) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n test('should return the same value if no arguments are passed', () => {\n const fn = vi.fn(Math.random)\n class MyClass { @Once() getId() { return fn() } }\n const instance = new MyClass()\n const id1 = instance.getId()\n const id2 = instance.getId()\n expect(id1).toStrictEqual(id2)\n expect(fn).toHaveBeenCalledTimes(1)\n expect(fn).toHaveBeenCalledWith()\n })\n\n test('should return the same values if different arguments are passed', () => {\n const fn = vi.fn((n = 0) => (n as number) + Math.random())\n class MyClass { @Once() getId(n: number) { return fn(n) } }\n const instance = new MyClass()\n const id1 = instance.getId(1)\n const id2 = instance.getId(1)\n const id3 = instance.getId(2)\n expect(id1).toStrictEqual(id2)\n expect(id1).toStrictEqual(id3)\n expect(fn).toHaveBeenCalledTimes(1)\n expect(fn).toHaveBeenCalledWith(1)\n expect(fn).not.toHaveBeenCalledWith(2)\n })\n\n test('should preserve the method context', () => {\n class MyClass { value = { foo: 42 }; @Once() getValue() { return this?.value } }\n const instance = new MyClass()\n const result1 = instance.getValue()\n const result2 = instance.value\n expect(result1).toBe(result2)\n })\n\n test('should have different results for different instances', () => {\n class MyClass { @Once() getValue() { return Math.random() } }\n const instance1 = new MyClass()\n const instance2 = new MyClass()\n const result1 = instance1.getValue()\n const result2 = instance2.getValue()\n expect(result1).not.toStrictEqual(result2)\n })\n}\n"],"names":["once"],"mappings":";;AAqBO,SAAS,OAA+C;AACtD,SAAA,CAAC,QAAQ,cAAc,eAAe;AAC3C,UAAM,SAAS,WAAW;AACf,WAAA,WAAA,QAAQA,KAAK,KAAA,MAAM,GACvB;AAAA,EAAA;AAEX;;"}
package/dist/Once.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Function, MethodDecorator } from '@unshared/types';
2
+
3
+ /**
4
+ * Decorate a method to memoize it's result. Meaning that if the method is called,
5
+ * it will return the same result without executing the method again, **even if the
6
+ * method is called with different arguments**.
7
+ *
8
+ * @returns The method descriptor.
9
+ * @example
10
+ * // Declare a class with a onced method.
11
+ * class Greeter {
12
+ * ->@Once()
13
+ * greet(name: string) { return `Hello, ${name}! - ${Math.random()}` }
14
+ * }
15
+ *
16
+ * // The first call to the method will be executed.
17
+ * const instance = new Greeter()
18
+ * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'
19
+ * instance.greet('Bob') // 'Hello, Alice! - 0.123456789'
20
+ */
21
+ declare function Once<T extends Function>(): MethodDecorator<T>;
22
+
23
+ export { Once };
package/dist/Once.js ADDED
@@ -0,0 +1,11 @@
1
+ import { once } from "@unshared/functions/once";
2
+ function Once() {
3
+ return (target, propertyName, descriptor) => {
4
+ const method = descriptor.value;
5
+ return descriptor.value = once(method), descriptor;
6
+ };
7
+ }
8
+ export {
9
+ Once
10
+ };
11
+ //# sourceMappingURL=Once.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Once.js","sources":["../Once.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { once } from '@unshared/functions/once'\n\n/**\n * Decorate a method to memoize it's result. Meaning that if the method is called,\n * it will return the same result without executing the method again, **even if the\n * method is called with different arguments**.\n *\n * @returns The method descriptor.\n * @example\n * // Declare a class with a onced method.\n * class Greeter {\n * ->@Once()\n * greet(name: string) { return `Hello, ${name}! - ${Math.random()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice') // 'Hello, Alice! - 0.123456789'\n * instance.greet('Bob') // 'Hello, Alice! - 0.123456789'\n */\nexport function Once<T extends Function>(): MethodDecorator<T> {\n return (target, propertyName, descriptor) => {\n const method = descriptor.value!\n descriptor.value = once(method) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n test('should return the same value if no arguments are passed', () => {\n const fn = vi.fn(Math.random)\n class MyClass { @Once() getId() { return fn() } }\n const instance = new MyClass()\n const id1 = instance.getId()\n const id2 = instance.getId()\n expect(id1).toStrictEqual(id2)\n expect(fn).toHaveBeenCalledTimes(1)\n expect(fn).toHaveBeenCalledWith()\n })\n\n test('should return the same values if different arguments are passed', () => {\n const fn = vi.fn((n = 0) => (n as number) + Math.random())\n class MyClass { @Once() getId(n: number) { return fn(n) } }\n const instance = new MyClass()\n const id1 = instance.getId(1)\n const id2 = instance.getId(1)\n const id3 = instance.getId(2)\n expect(id1).toStrictEqual(id2)\n expect(id1).toStrictEqual(id3)\n expect(fn).toHaveBeenCalledTimes(1)\n expect(fn).toHaveBeenCalledWith(1)\n expect(fn).not.toHaveBeenCalledWith(2)\n })\n\n test('should preserve the method context', () => {\n class MyClass { value = { foo: 42 }; @Once() getValue() { return this?.value } }\n const instance = new MyClass()\n const result1 = instance.getValue()\n const result2 = instance.value\n expect(result1).toBe(result2)\n })\n\n test('should have different results for different instances', () => {\n class MyClass { @Once() getValue() { return Math.random() } }\n const instance1 = new MyClass()\n const instance2 = new MyClass()\n const result1 = instance1.getValue()\n const result2 = instance2.getValue()\n expect(result1).not.toStrictEqual(result2)\n })\n}\n"],"names":[],"mappings":";AAqBO,SAAS,OAA+C;AACtD,SAAA,CAAC,QAAQ,cAAc,eAAe;AAC3C,UAAM,SAAS,WAAW;AACf,WAAA,WAAA,QAAQ,KAAK,MAAM,GACvB;AAAA,EAAA;AAEX;"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var throttle = require("@unshared/functions/throttle");
3
+ function Throttle(delay) {
4
+ return (target, propertyName, descriptor) => {
5
+ const method = descriptor.value;
6
+ return descriptor.value = throttle.throttle(method, delay), descriptor;
7
+ };
8
+ }
9
+ exports.Throttle = Throttle;
10
+ //# sourceMappingURL=Throttle.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Throttle.cjs","sources":["../Throttle.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { throttle } from '@unshared/functions/throttle'\n\n/**\n * Throttle a method so that it will only execute once every specified delay.\n * Useful for implementing spam protection. When the method is called, it will\n * execute immediately and then wait for the specified delay before it can be\n * called again.\n *\n * **Note:** This decorator will omit the return value of the method and return `undefined`.\n *\n * @param delay The delay in milliseconds to wait before executing the method.\n * @returns The method descriptor.\n * @example\n * // Declare a class with a debounced method.\n * class Greeter {\n * ->@Throttle(100)\n * greet(name: string) { return `Hello, ${name}! - ${Date.now()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice')\n * instance.greet('Bob')\n * instance.greet('Charlie')\n *\n * // The method will be called immediately and can only be called again after 100ms.\n * // => Hello, Alice!\n */\nexport function Throttle<T extends Function<void>>(delay: number): MethodDecorator<T> {\n return (target, propertyName, descriptor) => {\n const method = descriptor.value!\n descriptor.value = throttle(method, delay) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n beforeEach(() => vi.useFakeTimers)\n\n test('should call the function immediately', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n expect(fn).toHaveBeenCalledTimes(1)\n })\n\n test('should not call the function more than once within the delay', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n instance.fn()\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(1)\n })\n\n test('should pass the parameters of the first call to the function', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(10) fn(name: string) { fn(name) } }\n const instance = new Greeter()\n instance.fn('Alice')\n instance.fn('Bob')\n instance.fn('Charlie')\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledWith('Alice')\n })\n\n test('should call the function again after the delay has passed', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n vi.advanceTimersByTime(100)\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(2)\n })\n\n test('should return undefined', () => {\n const fn = vi.fn(() => 'foobar')\n class Greeter { @Throttle(100) fn() { return fn() } }\n const instance = new Greeter()\n const result = instance.fn()\n expect(result).toBeUndefined()\n })\n}\n"],"names":["throttle"],"mappings":";;AA6BO,SAAS,SAAmC,OAAmC;AAC7E,SAAA,CAAC,QAAQ,cAAc,eAAe;AAC3C,UAAM,SAAS,WAAW;AAC1B,WAAA,WAAW,QAAQA,SAAAA,SAAS,QAAQ,KAAK,GAClC;AAAA,EAAA;AAEX;;"}
@@ -0,0 +1,31 @@
1
+ import { Function, MethodDecorator } from '@unshared/types';
2
+
3
+ /**
4
+ * Throttle a method so that it will only execute once every specified delay.
5
+ * Useful for implementing spam protection. When the method is called, it will
6
+ * execute immediately and then wait for the specified delay before it can be
7
+ * called again.
8
+ *
9
+ * **Note:** This decorator will omit the return value of the method and return `undefined`.
10
+ *
11
+ * @param delay The delay in milliseconds to wait before executing the method.
12
+ * @returns The method descriptor.
13
+ * @example
14
+ * // Declare a class with a debounced method.
15
+ * class Greeter {
16
+ * ->@Throttle(100)
17
+ * greet(name: string) { return `Hello, ${name}! - ${Date.now()}` }
18
+ * }
19
+ *
20
+ * // The first call to the method will be executed.
21
+ * const instance = new Greeter()
22
+ * instance.greet('Alice')
23
+ * instance.greet('Bob')
24
+ * instance.greet('Charlie')
25
+ *
26
+ * // The method will be called immediately and can only be called again after 100ms.
27
+ * // => Hello, Alice!
28
+ */
29
+ declare function Throttle<T extends Function<void>>(delay: number): MethodDecorator<T>;
30
+
31
+ export { Throttle };
@@ -0,0 +1,11 @@
1
+ import { throttle } from "@unshared/functions/throttle";
2
+ function Throttle(delay) {
3
+ return (target, propertyName, descriptor) => {
4
+ const method = descriptor.value;
5
+ return descriptor.value = throttle(method, delay), descriptor;
6
+ };
7
+ }
8
+ export {
9
+ Throttle
10
+ };
11
+ //# sourceMappingURL=Throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Throttle.js","sources":["../Throttle.ts"],"sourcesContent":["import { Function, MethodDecorator } from '@unshared/types'\nimport { throttle } from '@unshared/functions/throttle'\n\n/**\n * Throttle a method so that it will only execute once every specified delay.\n * Useful for implementing spam protection. When the method is called, it will\n * execute immediately and then wait for the specified delay before it can be\n * called again.\n *\n * **Note:** This decorator will omit the return value of the method and return `undefined`.\n *\n * @param delay The delay in milliseconds to wait before executing the method.\n * @returns The method descriptor.\n * @example\n * // Declare a class with a debounced method.\n * class Greeter {\n * ->@Throttle(100)\n * greet(name: string) { return `Hello, ${name}! - ${Date.now()}` }\n * }\n *\n * // The first call to the method will be executed.\n * const instance = new Greeter()\n * instance.greet('Alice')\n * instance.greet('Bob')\n * instance.greet('Charlie')\n *\n * // The method will be called immediately and can only be called again after 100ms.\n * // => Hello, Alice!\n */\nexport function Throttle<T extends Function<void>>(delay: number): MethodDecorator<T> {\n return (target, propertyName, descriptor) => {\n const method = descriptor.value!\n descriptor.value = throttle(method, delay) as unknown as T\n return descriptor\n }\n}\n\n/* v8 ignore start */\nif (import.meta.vitest) {\n beforeEach(() => vi.useFakeTimers)\n\n test('should call the function immediately', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n expect(fn).toHaveBeenCalledTimes(1)\n })\n\n test('should not call the function more than once within the delay', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n instance.fn()\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(1)\n })\n\n test('should pass the parameters of the first call to the function', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(10) fn(name: string) { fn(name) } }\n const instance = new Greeter()\n instance.fn('Alice')\n instance.fn('Bob')\n instance.fn('Charlie')\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledWith('Alice')\n })\n\n test('should call the function again after the delay has passed', () => {\n const fn = vi.fn()\n class Greeter { @Throttle(100) fn() { fn() } }\n const instance = new Greeter()\n instance.fn()\n vi.advanceTimersByTime(100)\n instance.fn()\n vi.advanceTimersByTime(100)\n expect(fn).toHaveBeenCalledTimes(2)\n })\n\n test('should return undefined', () => {\n const fn = vi.fn(() => 'foobar')\n class Greeter { @Throttle(100) fn() { return fn() } }\n const instance = new Greeter()\n const result = instance.fn()\n expect(result).toBeUndefined()\n })\n}\n"],"names":[],"mappings":";AA6BO,SAAS,SAAmC,OAAmC;AAC7E,SAAA,CAAC,QAAQ,cAAc,eAAe;AAC3C,UAAM,SAAS,WAAW;AAC1B,WAAA,WAAW,QAAQ,SAAS,QAAQ,KAAK,GAClC;AAAA,EAAA;AAEX;"}
package/dist/index.cjs ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var Debounce = require("./Debounce.cjs"), Memoize = require("./Memoize.cjs"), Once = require("./Once.cjs"), Throttle = require("./Throttle.cjs");
3
+ require("@unshared/functions/debounce");
4
+ require("@unshared/functions/memoize");
5
+ require("@unshared/functions/once");
6
+ require("@unshared/functions/throttle");
7
+ exports.Debounce = Debounce.Debounce;
8
+ exports.Memoize = Memoize.Memoize;
9
+ exports.Once = Once.Once;
10
+ exports.Throttle = Throttle.Throttle;
11
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -0,0 +1,6 @@
1
+ export { Debounce } from './Debounce.js';
2
+ export { Memoize } from './Memoize.js';
3
+ export { Once } from './Once.js';
4
+ export { Throttle } from './Throttle.js';
5
+ import '@unshared/types';
6
+ import '@unshared/functions/memoize';
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import { Debounce } from "./Debounce.js";
2
+ import { Memoize } from "./Memoize.js";
3
+ import { Once } from "./Once.js";
4
+ import { Throttle } from "./Throttle.js";
5
+ import "@unshared/functions/debounce";
6
+ import "@unshared/functions/memoize";
7
+ import "@unshared/functions/once";
8
+ import "@unshared/functions/throttle";
9
+ export {
10
+ Debounce,
11
+ Memoize,
12
+ Once,
13
+ Throttle
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@unshared/decorators",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "license": "MIT",
6
+ "sideEffects": false,
7
+ "author": "Stanley Horwood <stanley@hsjm.io>",
8
+ "bugs": "https://github.com/shorwood/unshared/issues",
9
+ "homepage": "https://github.com/shorwood/unshared#readme",
10
+ "repository": {
11
+ "directory": "packages/decorators",
12
+ "type": "git",
13
+ "url": "git+ssh://https://github.com/shorwood/unshared"
14
+ },
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ "./Debounce": {
20
+ "require": "./dist/Debounce.cjs",
21
+ "types": "./dist/Debounce.d.ts",
22
+ "import": "./dist/Debounce.js"
23
+ },
24
+ "./Memoize": {
25
+ "require": "./dist/Memoize.cjs",
26
+ "types": "./dist/Memoize.d.ts",
27
+ "import": "./dist/Memoize.js"
28
+ },
29
+ "./Once": {
30
+ "require": "./dist/Once.cjs",
31
+ "types": "./dist/Once.d.ts",
32
+ "import": "./dist/Once.js"
33
+ },
34
+ "./Throttle": {
35
+ "require": "./dist/Throttle.cjs",
36
+ "types": "./dist/Throttle.d.ts",
37
+ "import": "./dist/Throttle.js"
38
+ },
39
+ ".": {
40
+ "require": "./dist/index.cjs",
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.js"
43
+ }
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "README.md",
48
+ "LICENSE.md"
49
+ ],
50
+ "dependencies": {
51
+ "@unshared/functions": "0.0.1",
52
+ "@unshared/types": "0.0.1"
53
+ }
54
+ }