Skip to content

Commit

Permalink
Add docs for targetMeta() decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
aedart committed Feb 2, 2024
1 parent d638228 commit b850b7c
Showing 1 changed file with 178 additions and 3 deletions.
181 changes: 178 additions & 3 deletions docs/archive/current/packages/support/meta.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ import {meta, getMeta} from '@aedart/support/meta';
@meta('service_alias', 'locationSearcher')
class Service {}

getMeta(Service, 'service_alias');
getMeta(Service, 'service_alias'); // locationSearcher
```

**Roughly "desugars" to the following:**
Expand All @@ -240,10 +240,185 @@ function meta(key, value) {
@meta('service_alias', 'locationSearcher')
class Service {}

Service[Symbol.metadata].service_alias;
Service[Symbol.metadata].service_alias; // locationSearcher
```
(_Above shown example is very simplified. Actual implementation is a bit more complex..._)

At present, the internal mechanisms of the `meta` decorator must rely on a [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) to associate metadata with the intended class.
When the [Decorator Metadata proposal](https://github.com/tc39/proposal-decorator-metadata) becomes more mature and transpilers offer the `context.metadata` object (_or when browsers support it_),
then this decorator will be updated respectfully to use the available metadata object.
then this decorator will be updated respectfully to use the available metadata object.

## Target Meta <Badge type="tip" text="Available since v0.7" vertical="middle" />

The `targetMeta()` decorator offers the ability to associate metadata directly with a class instance or class method reference.
This can be useful in situations when you do not know the class that owns the metadata.

::: tip Supported Elements

Unlike the [`meta()` decorator](#supported-elements), `targetMeta()` only supports the following elements:

* `class`
* `method`

:::

**Example: class instance**

```js
import {targetMeta, getTargetMeta} from '@aedart/support/meta';

@targetMeta('description', { type: 'Search Service', alias: 'Location Sercher' })
class LocationSearcherService {}

const instance = new LocationSearcherService();

// ...later in your application...
getTargetMeta(instance, 'description')?.type; // Search Service
```
**Example: method reference**
```js
import {targetMeta, getTargetMeta} from '@aedart/support/meta';

class LocationSearcherService {

@targetMeta('dependencies', [ 'httpClient' ])
search(apiClient) {}
}

const instance = new LocationSearcherService();

// ...later in your application...
getTargetMeta(instance.search, 'dependencies'); // [ 'httpClient' ]
```
### Inheritance
Target meta is automatically inherited by subclasses and can also be overwritten, similar to that of the [`meta()` decorator](#inheritance).
**Example: classes**
```js
import {targetMeta, getTargetMeta} from '@aedart/support/meta';

@meta('service_alias', 'locationSearcher')
class Service {}

class CitySearcher extends Service {}

const instance = new CitySearcher();

// ...later in your application...
getTargetMeta(instance, 'service_alias'); // locationSearcher
```
**Example: methods**
```js
import {targetMeta, getTargetMeta} from '@aedart/support/meta';

class Service {

@targetMeta('dependencies', [ 'countrySearchApiClient' ])
search(apiClient) {
// ...not shown...
}
}

class CountrySearcher extends Service {
// ... not method overwrite here...
}

class CitySearcher extends Service {

@targetMeta('dependencies', [ 'citySearchApiClient' ])
search(apiClient) {
// ...not shown...
}
}

const instanceA = new Service();
const instanceB = new CountrySearcher();
const instanceC = new CitySearcher();

// ...later in your application...
getTargetMeta(instanceA.search, 'dependencies'); // [ 'countrySearchApiClient' ]
getTargetMeta(instanceB.search, 'dependencies'); // [ 'countrySearchApiClient' ]
getTargetMeta(instanceC.search, 'dependencies'); // [ 'citySearchApiClient' ]
```
#### Static Methods
Inheritance for static methods works a bit differently. By default, any subclass will automatically inherit target metadata, even for static methods.
However, if you overwrite the given static method, the metadata is lost.
::: tip Limitation
_When a static method is overwritten, the parent's "target" metadata cannot be obtained due to a general limitation of the `meta()` decorator.
The decorator has no late `this` binding available to the overwritten static method.
This makes it impossible to associate the overwritten static method with metadata from the parent._
:::
**Example: inheritance for static methods**
```js
import {targetMeta, getTargetMeta} from '@aedart/support/meta';

class Service {

@targetMeta('dependencies', [ 'xmlClient' ])
static search(client) {
// ...not shown...
}
}

class CountrySearcher extends Service {
// ... not method overwrite here...
}

class CitySearcher extends Service {

// Overwite of static method - target meta is lost
static search(client) {}
}

// ...later in your application...
getTargetMeta(CountrySearcher.search, 'dependencies'); // [ 'xmlClient' ]
getTargetMeta(CitySearcher.search, 'dependencies'); // undefined
```
To overcome the above shown issue, you can use the `inheritTargetMeta()` decorator. It forces the static method to "copy" metadata from its parent, if available.
**Example: force inheritance for static methods**
```js
import {
targetMeta,
getTargetMeta,
inheritTargetMeta
} from '@aedart/support/meta';

class Service {

@targetMeta('dependencies', [ 'xmlClient' ])
static search(client) {
// ...not shown...
}
}

class CountrySearcher extends Service {
// ... not method overwrite here...
}

class CitySearcher extends Service {

@inheritTargetMeta()
static search(client) {}
}

// ...later in your application...
getTargetMeta(CountrySearcher.search, 'dependencies'); // [ 'xmlClient' ]
getTargetMeta(CitySearcher.search, 'dependencies'); // [ 'xmlClient' ]
```

0 comments on commit b850b7c

Please sign in to comment.