From b850b7c2e5eb21583cf49e4ca6edc9042b5c7af0 Mon Sep 17 00:00:00 2001 From: alin Date: Fri, 2 Feb 2024 14:56:32 +0100 Subject: [PATCH] Add docs for targetMeta() decorator --- docs/archive/current/packages/support/meta.md | 181 +++++++++++++++++- 1 file changed, 178 insertions(+), 3 deletions(-) diff --git a/docs/archive/current/packages/support/meta.md b/docs/archive/current/packages/support/meta.md index 19f8e6f3..b804e6e7 100644 --- a/docs/archive/current/packages/support/meta.md +++ b/docs/archive/current/packages/support/meta.md @@ -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:** @@ -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. \ No newline at end of file +then this decorator will be updated respectfully to use the available metadata object. + +## Target Meta + +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' ] +``` \ No newline at end of file