ts-delegate
Version:
Helper annotations, types, and methods for type-safe and JSDoc-preserving delegation in TypeScript
183 lines (159 loc) • 5.29 kB
Markdown
# ts-delegate
Helper annotations, types, and methods for type-safe and JSDoc-preserving delegation in TypeScript
## Installation
Run command: `npm i ts-delegate`<br>
<br>
This library also requires these options in tsconfig.json<br>
```json
{
"compilerOptions": {
...
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
...
}
```
### Without annotations:
If you can't use annotations, you can replace any documented case of () with this:
```ts
class MyClass {
method() {
...
}
static method2() {
...
}
constructor() {
// this.method() will now be delegated when delegate() is used on this class
(this.method as any).isDelegated = true;
}
}
// for static methods
(typeof MyClass.method2).isDelegated = true;
(typeof MyClass.method2).omitFirst = true;
```
## What It Does
`ts-delegate` helps you:
- Delegate methods from other classes without duplicating JSDoc or type definitions.
- Preserve type safety for delegated methods.
- Delegate static methods into instances and optionally omit the first argument to pass `this`.
## Usage
### Marking methods for delegation
Methods can be marked for delegation with (omitFirst?)
#### Instance
Does not need any arguments; supplying them won't do anything
```ts
class MyClass {
// when doing Delegate<[MyClass]> this will be delegated
()
method() {
...
}
// but this one will not
thisWontBeDelegated() {
...
}
}
```
#### Static
For static functions, you can do the same, and optionally add `true` for omitting the first argument
```ts
class Static {
()
static method1() {
...
}
// when this method is called in delegated method MyClass#method2, it will call with the MyClass instance as the first argument
(true)
static method2(other: MyClass) {
...
}
}
```
### Marking fields for delegation
Fields can be marked for delegation with (get?, set?)<br>
Get and set default to true. When true, it allows that functonality.<br>
If get is false, the field will return undefined. If set is false, the field can't be changed by the instance delegating the field.<br>
The functionality is identical for static fields.
```ts
class MyClass {
// this can be read and edited by the delegating class
()
field: number = 100;
// this cannot be changed by the delegating class
(true, false)
constant: number = 9458694;
()
static STATIC_CONSTANT: string = "hello world!";
}
```
#### Instance
### Implementing delegated values
You can delegate with a delegation function and a type implement
#### Instance
For delegating instances, you do Delegate<[array of classes]> and delegate(this, [array of instances]);
```ts
class OtherClass {
()
myMethod() {
...
}
()
field: number = 100;
}
class MyClass implements Delegate<[OtherClass]> {
constructor() {
delegate(this, [new OtherClass()]);
this.myMethod();
console.log(this.field);
}
// This will confirm to typescript that it exists and will clone the structure of the other method, this still retains JSDoc and all functionality
declare myMethod: OtherClass['myMethod'];
// reading this will print the field from the OtherClass instance
// setting will change it on both
declare field: OtherClass['field'];
}
```
#### Static
For delegating static classes, you do DelegateStatic<[array of classes], (this)> with (this) as the class implementing it, and delegateStatic(this, [array of classes]);
```ts
class StaticClass {
// delegating will add this.add(a, b);
()
static add(a: number, b: number): number {
return a + b;
}
// This will omit the first argument and it will be called with `this` in MyClass
// This is useful if you want to have helper classes in different files that can interact with your class without requiring an instance of the helper class to be made
// delegating will then add this.change(to); without 'myClass'
(true)
static change(myClass: MyClass, to: number): void {
myClass.value = to;
}
// field can be read and changed through anything delegating this
()
static field: number = 9548693458;
}
class MyClass implements DelegateStatic<[StaticClass], MyClass> {
value: number = 0;
constructor() {
delegateStatic(this, [StaticClass]);
console.log(this.add(5, 10)) // 15
// first argument is omitted and replaced with `this`
this.change(100);
console.log(this.value); // 100
// will read from the static
console.log(this.field); // 9548693458
this.field = 10;
console.log(StaticClass.field); // 10
}
// declare for static requires typeof
declare add: typeof StaticClass.add;
// OmitFirst<> to omit the first argument for this
declare change: OmitFirst<typeof StaticClass.change>;
// the field will be accessible and writable
declare field: typeof StaticClass.field;
}
```
#### Instance