Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addons #1755

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft

Addons #1755

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/addons/src/decorators/auto-bind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function autoBind(_: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
const originalMethod = descriptor.value;
return {
configurable: true,
get() {
const boundFn = originalMethod.bind(this);
Object.defineProperty(this, propertyKey, {
value: boundFn,
configurable: true,
writable: true,
});
return boundFn;
},
};
}
28 changes: 28 additions & 0 deletions packages/addons/src/decorators/call-once.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ICustomElementViewModel } from '@aurelia/runtime-html';

/**
* Decorator that ensures a method is called only once no matter how many times it's invoked.
* Caches and returns the result of the first call for all future invocations.
* @param errorMessage Error message to display in case of concurrent calls (optional).
*/
export function callOnce(errorMessage = ''): MethodDecorator {
const cache = new WeakMap<object, unknown>();
return function (target: ICustomElementViewModel, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
if (typeof descriptor.value !== 'function') {
throw new Error(`@callOnce can only be used on methods, not "${String(propertyKey)}".`);
}

const originalMethod = descriptor.value;
descriptor.value = function (...args: unknown[]) {
if (cache.has(this)) {
if (errorMessage) {
console.warn(`Warning: ${String(propertyKey)} ${errorMessage}`);
}
return cache.get(this);
}
const result = originalMethod.apply(this, args);
cache.set(this, result);
return result;
};
} as MethodDecorator;
}
31 changes: 31 additions & 0 deletions packages/addons/src/decorators/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* A decorator function that implements debounce functionality on a method.
*
* @param {number} wait - The number of milliseconds to wait before invoking the wrapped method.
* @returns {Function} - Returns a decorator function that can be applied to a class method.
*/
export function debounce(wait: number) {

/**
* A decorator function that wraps a class method with debounce functionality.
*
* @param {Object} target - The object being decorated.
* @param {string|symbol} propertyKey - The name of the method being decorated.
* @param {PropertyDescriptor} descriptor - The descriptor for the method being decorated.
* @returns {PropertyDescriptor} - Returns the updated descriptor for the decorated method.
*/
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

let timeout: number;
const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {
clearTimeout(timeout);
timeout = setTimeout(() => {
originalMethod.apply(this, args);
}, wait);
};

return descriptor;
};
}
9 changes: 9 additions & 0 deletions packages/addons/src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export * from './auto-bind';
export * from './call-once';
export * from './debounce';
export * from './log';
export * from './memoize';
export * from './readonly';
export * from './throttle';
export * from './timing';
export * from './validate';
26 changes: 26 additions & 0 deletions packages/addons/src/decorators/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Returns a decorator function that logs method calls with their respective arguments and results.
*
* @param {Object} target - The object being decorated.
* @param {string|symbol} propertyKey - The name of the method being decorated.
* @param {TypedPropertyDescriptor<any>} descriptor - The method's property descriptor.
* @returns {TypedPropertyDescriptor<any>} - The modified property descriptor.
*/
export function log(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value;

/**
* Logs the method call with its arguments and result, then returns the result.
*
* @param {...any} args - The arguments passed to the method.
* @returns {any} - The result of the method.
*/
descriptor.value = function(...args: any[]) {
console.log(`Method ${propertyKey.toString()} was called on type ${target.constructor.name} with arguments: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey.toString()} was called on type ${target.constructor.name} with result: ${JSON.stringify(result)}`);
return result;
};

return descriptor;
};
28 changes: 28 additions & 0 deletions packages/addons/src/decorators/memoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export function memoize() {
const cache = new Map();
return function(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;

descriptor.value = async function(...args: any[]) {
const cacheKey = JSON.stringify(args);

if (cache.has(cacheKey)) {
return await cache.get(cacheKey);
}

const promise = originalMethod.apply(this, args);
cache.set(cacheKey, promise);

try {
const result = await promise;
cache.set(cacheKey, result);
return result;
} catch (error) {
cache.delete(cacheKey);
throw error;
}
};

return descriptor;
};
}
11 changes: 11 additions & 0 deletions packages/addons/src/decorators/readonly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* A decorator function that sets a property to be readonly.
*
* @param {Object} target - The object being decorated.
* @param {string|symbol} key - The name of the property being decorated.
*/
export function readonly(target: any, key: string | symbol) {
Object.defineProperty(target, key, {
writable: false
});
}
29 changes: 29 additions & 0 deletions packages/addons/src/decorators/throttle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Returns a decorator function that can be used to throttle a method from being executed too frequently.
*
* @param {number} time - The amount of time (in milliseconds) to wait before allowing the method to execute again.
* @returns {Function} - The decorator function.
*/
export function throttle(time: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let timeout:number | undefined;

/**
* Executes the annotated method only if it hasn't been called within the specified time period.
*
* @param {...any} args - The arguments passed to the method.
* @returns {any} - The result of the method (or undefined if the method was throttled).
*/
descriptor.value = function(...args: any[]) {
if (!timeout) {
timeout = setTimeout(() => {
clearTimeout(timeout);
timeout = undefined;
}, time);
return descriptor.value.apply(this, args);
}
}

return descriptor;
};
}
29 changes: 29 additions & 0 deletions packages/addons/src/decorators/timing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Returns a decorator function that can be used to measure the time it takes to execute a method.
*
* @returns {Function} - The decorator function.
*/
export function timing() {
return function (
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>
) {
const originalMethod = descriptor.value;

/**
* Executes the annotated method, measures the execution time and logs it to the console.
*
* @param {...any} args - The arguments passed to the method.
* @returns {any} - The result of the method.
*/
descriptor.value = function (...args: any[]) {
console.time(`Method ${propertyKey.toString()} was called on type ${target.constructor.name}`);
const result = originalMethod.apply(this, args);
console.timeEnd(`Method ${propertyKey.toString()} was called on type ${target.constructor.name}`);
return result;
};

return descriptor;
}
}
49 changes: 49 additions & 0 deletions packages/addons/src/decorators/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Returns a decorator function that can be used to validate the parameters of a given method.
*
* @param {ParameterConstraints} constraints - An object containing the parameter constraints.
* @returns {Function} - The decorator function.
*/
export function validate(constraints: ParameterConstraints) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;

descriptor.value = function (...args: any[]) {
for (let i = 0; i < args.length; i++) {
const argValue = args[i];
const argName = Object.keys(constraints)[i];
const argConstraints = constraints[argName];

if (argConstraints.required && argValue === undefined) {
throw new Error(`${argName} is required`);
}

if (typeof argValue !== argConstraints.type) {
throw new Error(`${argName} must be of type ${argConstraints.type}`);
}
}

return originalMethod.apply(this, args);
};

return descriptor;
};
}

/**
* Defines the structure of an object containing parameter constraints.
*
* @interface
*/
export interface ParameterConstraints {
[parameterName: string]: {
/** Specifies whether the parameter is required. */
required: boolean;
/** Specifies the expected type of the parameter. */
type: 'string' | 'number' | 'boolean' | 'object' | 'function';
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '(PART)', symbols: '()' })
export class AngularEventBindingAttributePattern {
public ['(PART)'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'trigger');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './event-binding';
export * from './one-way';
export * from './ref';
export * from './two-way';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '[PART]', symbols: '[]' })
export class AngularOneWayBindingAttributePattern {
public ['[PART]'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'to-view' /*'bind'*/);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '#PART', symbols: '#' })
export class AngularSharpRefAttributePattern {
public ['#PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, parts[0], 'element', 'ref');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '[(PART)]', symbols: '[()]' })
export class AngularTwoWayBindingAttributePattern {
public ['[(PART)]'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'two-way');
}
}
1 change: 1 addition & 0 deletions packages/addons/src/frameworks/angular/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as AngularCompatibilityPlugin from './attribute-bindings';
3 changes: 3 additions & 0 deletions packages/addons/src/frameworks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './angular';
export * from './svelte';
export * from './vue';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: 'bind:PART', symbols: 'bind:' })
export class SveleteEventBindingAttributePattern {
public ['bind:PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'trigger');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './event-binding';
export * from './one-way';
export * from './ref';
export * from './two-way';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '[PART]', symbols: ':' })
export class VueOneWayBindingAttributePattern {
public [':PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'to-view' /*'bind'*/);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '#PART', symbols: '#' })
export class AngularSharpRefAttributePattern {
public ['#PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, parts[0], 'element', 'ref');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '::PART', symbols: '::' })
export class DoubleColonTwoWayBindingAttributePattern {
public ['::PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'two-way');
}
}

@attributePattern({ pattern: 'v-model', symbols: '' })
export class VueTwoWayBindingAttributePattern {
public ['v-model'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, 'value', 'two-way');
}
}
1 change: 1 addition & 0 deletions packages/addons/src/frameworks/svelte/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as SveleteCompatibilityPlugin from './attribute-bindings';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '@PART', symbols: '@' })
export class VueEventBindingAttributePattern {
public ['@PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'trigger');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './event-binding';
export * from './one-way';
export * from './ref';
export * from './two-way';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AttrSyntax, attributePattern } from '@aurelia/runtime-html';

@attributePattern({ pattern: '[PART]', symbols: ':' })
export class VueOneWayBindingAttributePattern {
public [':PART'](rawName: string, rawValue: string, parts: string[]): AttrSyntax {
return new AttrSyntax(rawName, rawValue, parts[0], 'to-view' /*'bind'*/);
}
}
Loading