diff --git a/404.html b/404.html new file mode 100644 index 00000000..a00194d2 --- /dev/null +++ b/404.html @@ -0,0 +1,37 @@ + + +
+ + + + + +Be nice... be fair! Writing free Open Source software often means spending time developing after-hours, and in weekends. Chances are pretty good that you yourself are involved in one or many Open Source projects and already know how time-consuming it can be. In any case, the following constitutes the code of conduct used by Ion:
Participants can be subject to consequences, if the code of conduct is not upheld. Such consequences include, but not limited to, warnings, and banning from further participation.
Have you found a defect ( bug or design flaw ), or do you wish improvements? In the following sections, you might find some useful information on how you can help this project. In any case, I thank you for taking the time to help me improve this project's deliverables and overall quality.
If you have found a bug, please report it on GitHub. When reporting the bug, do consider the following:
When time permits it, I will review your issue and take action upon it.
Please read the Security Policy.
If you have an idea for a new feature or perhaps changing an existing, feel free to create a feature request. Should you feel unsure whether your idea is good or not, then perhaps you could start a discussion.
If you wish to fix a bug, add new feature, or perhaps change an existing, then please follow this guideline
As soon as I receive the pull-request (and have time for it), I will review your changes and merge them into this project. If not, I will inform you why I choose not to.
DANGER
Ion is still in development. You SHOULD NOT use any of the packages in a production environment. Breaking changes MUST be expected for all v0.x
releases!
Please review the CHANGELOG.md
for additional details.
Version | TypeScript | ECMA Script | Release | Security Fixes Until |
---|---|---|---|---|
1.x | 5.0 - ? | TBD | TBD | TBD |
0.x * | 5.0 | ES2022 | ongoing releases | until v1.x release |
*: current supported version.
TBD: "To be decided".
v0.x
HighlightsIntended as an alternative to mixins, the Concerns submodule offers a different way to overcome some of the limitations of single inheritance.
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+// A concern class...
+class Role extends AbstractConcern {
+ addRole(name) {
+ /* ...not shown... */
+ }
+}
+
+// Use concern in target class...
+@use(Role)
+class User {}
+
+// Later in your application...
+const user = new User();
+user.addRole('maintainer');
+user.addRole('supporter');
+
Objects merge utility, using deep copy.
import { merge } from "@aedart/support/objects";
+
+const a = {
+ 'name': 'Alin',
+};
+
+const b = {
+ 'address': {
+ 'street': 'Northern Street 1'
+ },
+};
+
+const result = merge(a, b); // { 'name': 'Alin', 'address': { 'street': '...' } }
+
Adaptation of Justin Fagnani's mixwith.js
.
import { mix, Mixin } from "@aedart/support/mixins";
+
+const NameMixin = Mixin((superclass) => class extends superclass {
+ #name;
+
+ set name(value) {
+ this.#name = value;
+ }
+
+ get name() {
+ return this.#name;
+ }
+});
+
+class Item extends mix().with(
+ NameMixin
+) {}
+
+// ...Later in your application
+const item = new Item();
+item.name = 'My Item';
+
+console.log(item.name); // My Item
+
See details and more examples in the @aedart/support/mixins
documentation.
Associate arbitrary metadata directly with the target element that is being decorated. See target meta decorator fro additional details.
import {targetMeta, getTargetMeta} from '@aedart/support/meta';
+
+class Service {
+
+ @targetMeta('desc', 'Seaches for cities')
+ search() {
+ // ...not shown...
+ }
+}
+
+const instance = new Service();
+
+// ...later in your application...
+getTargetMeta(instance.search, 'desc'); // Seaches for cities
+
The meta decorator is able to associate arbitrary metadata with a class and its elements.
import {meta, getMeta} from '@aedart/support/meta';
+
+@meta('description', 'Able to search for locations')
+class Service {}
+
+getMeta(Service, 'description'); // Able to search for locations
+
A package intended to contain various helpers and utilities. At the moment, the package comes with a few object utilities. See package documentation for more details.
Utilities for vuepress sites, which includes an Archive
component for structuring documentation into an archive. See package documentation for details.
For many years, I was too discouraged and overburden to publish my JavaScript as packages. Having a full-time job and maintaining Athenaeum took most of my time. Like so many other developers, I was copying JavaScript from one project into another. I always knew that was very bad practice. But, it was too cumbersome trying to maintain so much code, spread throughout various repositories.
Nevertheless, in recent years, Lerna, TypeScript, and many other tools & frameworks made it somewhat easier to manage mono-repos. I cannot claim to have gained more time, but decided to give JavaScript packages another shoot.
"[...] An atom or a group of atoms that has an electric charge [...] - positive or negative" ("Scientific definition" from dictionary.com)
The contracts package contains types, interfaces and unique identifiers.
More information available at a later point...
npm install --save-peer @aedart/contracts
+
yarn add --peer @aedart/contracts
+
pnpm add --save-peer @aedart/contracts
+
In here, you will find documentation for the available packages. A few things that might be good to know:
Badges are used to indicate the environment that a package is intended for, e.g. NodeBrowser.
You are of course welcome to use a package in a different environment than its original intent, if it is possible.
The "available since x.y.z" badge might also be displayed, if a package or feature was released during a minor version. E.g. Available since v1.5.
Sometimes, documentation might be available for a package that has not yet been released. Usually it is tagged with a Not Released badge, unless the package documentation is located in "next" release".
In rare situations, a package might only exist in the mono-repository and not published to npm's registry. Such a package is either experimental or internal, which means that it might not ever be published. Usually, it will be tagged with Internal (not published)Experimental, or similar badges, if documentation is made available about the package!
includesAll
Determines if an array includes all values.
import { includesAll } from '@aedart/support/arrays';
+
+const arr = [ 1, 2, 3 ];
+
+includesAll(arr, [ 1, 2 ]); // true
+includesAll(arr, [ 1, 4 ]); // false
+
includesAny
Determines if an array includes some values.
import { includesAny } from '@aedart/support/arrays';
+
+const arr = [ 1, 2, 3 ];
+
+includesAll(arr, [ 4, 2 ]); // true
+includesAll(arr, [ 5, 5 ]); // false
+
@aedart/support/arrays
contains array related utilities.
isArrayLike
Determines if a value is "array-like".
(isArrayLike()
is an alias for Lodash's isArrayLike.)
import { isArrayLike } from '@aedart/support/arrays';
+
+isArrayLike([]); // true
+isArrayLike('abc'); // true
+isArrayLike(new String('abc')); // true
+isArrayLike({ length: 0 }); // true
+isArrayLike(new Int8Array()); // true
+
+isArrayLike({}); // false
+isArrayLike(function() {}); // false
+isArrayLike(new Boolean(true)); // false
+isArrayLike(123); // false
+isArrayLike(new Number(123)); // false
+// ...etc
+
See also isSafeArrayLike()
.
isConcatSpreadable
Determines if object contains the well-known symbol Symbol.isConcatSpreadable
.
import { isConcatSpreadable } from '@aedart/support/arrays';
+
+isConcatSpreadable(null); // false
+isConcatSpreadable([ 1, 2, 3 ]); // false
+isConcatSpreadable({}); // false
+
+// -------------------------------------------------------------------------
+
+const arr = [ 1, 2, 3 ];
+arr[Symbol.isConcatSpreadable] = true;
+isConcatSpreadable(arr); // true
+
+// -------------------------------------------------------------------------
+
+const obj = {
+ [Symbol.isConcatSpreadable]: true,
+
+ // NOTE: length should be present, if Symbol.isConcatSpreadable
+ // set to `true` However, isConcatSpreadable() does not check
+ // if `length` is set!
+ length: 3,
+ 0: 'a',
+ 1: 'b',
+ 2: 'c'
+};
+isConcatSpreadable(obj); // true
+
+// -------------------------------------------------------------------------
+
+class A {}
+class B {
+ [Symbol.isConcatSpreadable] = false;
+}
+isConcatSpreadable(new A()); // false
+isConcatSpreadable(new B()); // true
+
isSafeArrayLike
Determines if value is "safe" array-like object. In this context "safe" means that value is not a string, not an instance of String
object, and not a Typed Array object.
import { isSafeArrayLike } from '@aedart/support/arrays';
+
+isSafeArrayLike([]); // true
+isSafeArrayLike({ length: 0 }); // true
+
+isSafeArrayLike('abc'); // false
+isSafeArrayLike(new String('abc')); // false
+isSafeArrayLike(new Int8Array()); // false
+// ...etc
+
isTypedArray
Determines if target is an instance of a TypedArray
.
import { isTypedArray } from '@aedart/support/arrays';
+
+isTypedArray(null); // false
+isTypedArray({}); // false
+isTypedArray([]); // false
+isTypedArray(new Map()); // false
+
+isTypedArray(new Int8Array()); // true
+isTypedArray(new Uint8Array()); // true
+isTypedArray(new Uint8ClampedArray()); // true
+isTypedArray(new Int16Array()); // true
+isTypedArray(new Uint16Array()); // true
+isTypedArray(new Int32Array()); // true
+isTypedArray(new Uint32Array()); // true
+isTypedArray(new Float32Array()); // true
+isTypedArray(new Float64Array()); // true
+isTypedArray(new BigInt64Array()); // true
+isTypedArray(new BigUint64Array()); // true
+
merge
Merges arrays into a new array. This function attempts to deep copy values, using structuredClone
.
import { merge } from '@aedart/support/arrays';
+
+const a = [ 1, 2, 3 ];
+const b = [ 4, 5, 6 ];
+const c = [ 7, 8, 9 ];
+
+merge(a, b, c); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
+
Simple (or "plain") objects deep copied. This means that new objects are returned in the resulting array.
See Mozilla's documentation for additional information about what data types are supported.
const a = { foo: 'foo' };
+const b = { bar: 'bar' };
+const c = { ping: 'pong' };
+
+const result = merge([ a ], [ b, c ]);
+
+console.log(result[0] === a); // false
+console.log(result[1] === b); // false
+console.log(result[2] === c); // false
+
In situations when values cannot be copied via structuredClone
, an ArrayMergeError
is thrown.
const a = [ 1, 2, 3 ];
+const b = [ function() {} ]; // A function cannot be deep copied...
+
+merge(a, b); // ArrayMergeError
+
In this context, an "alias" is a proxy property or method inside a target class. It is responsible for forwarding interaction to the original property or method, inside the concern class instance. Aliases are created automatically by the use()
class decorator.
When injecting a concern into a target class, the concern's public properties and methods are defined as "aliases" (aka. proxy properties or methods), in the target class' prototype (see PROVIDES
symbol for additional details).
Consider the following example:
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+class Levels extends AbstractConcern {
+ get level() { /* ...not shown */ }
+ set level(value) { /* ...not shown */ }
+ clear() { /* ...not shown */ }
+}
+
+@use(Levels)
+class Recorder {}
+
The aliasing mechanism will transform the target class into something that very roughly corresponds to this:
import {
+ use,
+ CONCERNS,
+ AbstractConcern
+} from "@aedart/support/concerns";
+
+class Levels extends AbstractConcern {
+ get level() { /* ...not shown */ }
+ set level(value) { /* ...not shown */ }
+ clear(level) { /* ...not shown */ }
+}
+
+class Recorder {
+ // ...private concerns container not shown...
+
+ // get level "alias"
+ get level() {
+ return this[CONCERNS].get(Levels)['level'];
+ }
+
+ // set level "alias"
+ set level(value) {
+ this[CONCERNS].get(Levels)['level'] = value;
+ }
+
+ // method clear "alias"
+ clear(...args) {
+ return this[CONCERNS].get(Levels)['clear'](...args);
+ }
+}
+
See Manual interaction and Conflict Resolution for additional details.
When a property or method from a concern already exists in the target class' prototype chain¹, then NO Alias is defined. Said differently, the use()
class decorator does NOT overwrite a target class' properties or methods.
class Label extends AbstractConcern {
+ get name() { /* ...not shown.. */ }
+ set name(v) { /* ...not shown.. */ }
+}
+
+@use(Label) // Label's "name" property is NOT aliased
+class Battery {
+
+ // Battery's get/set "name" remains untouched by concern
+ get name() { /* ...not shown.. */ }
+ set name(v) { /* ...not shown.. */ }
+}
+
¹: Inherited properties and methods are also respected.
See Conflict Resolution for additional details.
By default, a concern class is ONLY instantiated when you interact with its properties or methods, which have been "aliased" into a target class (aka. lazy booting).
class ContactsApi extends AbstractConcern {
+ get users() { /* ...not shown here... */}
+}
+
+@use(ContactsApi) // Concern is NOT instantiated
+class UsersRegistry {}
+
+const users = (new UsersRegistry()).users; // Concern is instantiated
+
You can use the bootConcerns()
utility to manually boot concerns. It accepts the following arguments:
instance: object|Owner
- The target class instance that uses the concerns....concerns: ConcernConstructor[]
- List of concern classes to instantiate (aka. boot).import { use, bootConcerns } from "@aedart/support/concerns";
+
+@use(
+ ConcernA,
+ ConcernB,
+ ConcernC,
+)
+class Target {
+ constructor() {
+ bootConcerns(this, ConcernA, ConcernB);
+ }
+}
+
+const instance = new Target(); // ConcernA and ConcernB are instantiated
+
WARNING
If you attempt to boot a concern that has already been booted, a BootError
will be thrown!
To determine if a concern has already been booted, use the concern container's hasBooted()
method.
import {
+ getContainer,
+ bootConcerns
+} from "@aedart/support/concerns";
+
+class Record extends ApiService {
+ constructor() {
+ super();
+
+ if (!getContainer(this).hasBooted(ApiConnection)) {
+ bootConcerns(this, ApiConnection);
+ }
+ }
+}
+
See Manual interaction for details.
If you wish to boot all concerns, use the bootAllConcerns()
utility.
import { use, bootAllConcerns } from "@aedart/support/concerns";
+
+@use(
+ ConcernA,
+ ConcernB,
+ ConcernC,
+)
+class Target {
+ constructor() {
+ bootAllConcerns(this);
+ }
+}
+
+const instance = new Target(); // All concerns are initialised
+
This chapter shows how to create a new concern class.
AbstractConcern
To create a new concern class, you can inherit from the AbstractConcern
class.
import { AbstractConcern } from "@aedart/support/concerns";
+
+class MyConcern extends AbstractConcern {
+
+ get message() { /* ...not shown */ }
+ set message(message) { /* ...not shown */ }
+
+ foo() { /* ...not shown */ }
+
+ [MY_SYMBOL]() { /* ...not shown */ }
+}
+
By default, all the public properties and methods (all property keys) are made available for "aliasing" into a target class. To configure which members should be made available for aliasing, see Customise "alias" members.
Note
Private methods and properties are NEVER "aliased" into a target class.
Note
Static methods and properties are NOT "aliased" into target class.
At the moment, such a feature is not supported by the concerns' mechanism.This may become available in the future, but presently you SHOULD NOT rely on static members becoming available for aliasing.
Caution
Some transpilers, like Babel and TypeScript, automatically move property declarations into the class' constructor
. For instance:
class A {
+ foo = 'bar';
+}
+
becomes like the following, after it has been transpiled:
class A {
+ constructor() {
+ this.foo = 'bar';
+ }
+}
+
When this happens, properties cannot be "aliased". The concern mechanisms relies on the class' prototype for reading what properties are available. To overcome such an issue, you can use getters and setters instead.
class A {
+ #foo = 'bar';
+
+ get foo() {
+ return this.#foo
+ }
+
+ set foo(v) {
+ this.#foo = v;
+ }
+}
+
If you wish to customise what properties and methods should be available for aliasing when used by a target class, overwrite the static PROVIDES
method.
import { AbstractConcern, PROVIDES } from "@aedart/support/concerns";
+
+class MyConcern extends AbstractConcern {
+
+ get message() { /* ...not shown */ }
+ set message(message) { /* ...not shown */ }
+ foo() { /* ...not shown */ }
+ [MY_SYMBOL]() { /* ...not shown */ }
+
+ static [PROVIDES]() {
+ // Make "message" and "foo" available for aliasing...
+ return [
+ 'message',
+ 'foo'
+ ];
+ }
+}
+
WARNING
Even if you do customise what properties and methods are available for aliasing, the returned property keys of the PROVIDES
method can be overruled, via conflict resolution!
If you truly wish to prevent certain properties or methods from being aliased into a target class, then you should declare them as private.
The concernOwner
property gives you direct access to the target class instance, in your concern. This allows you to create interaction between the target instance and your concern, which can be usefully in any number of situations. For instance, you can use the concernOwner
to create a fluent design of your utilities.
class ConcernsPeople extends AbstractConcern {
+ with(value) {
+ // ...not shown here...
+
+ return this.concernOwner;
+ }
+}
+
+@use(ConcernsPeople)
+class Invitation {
+ invite() { /* ...not shown here... */ }
+}
+
+const party = new Invitation();
+party
+ .with('Anna')
+ .with('Anders')
+ .with('Ulla')
+ .with('Jimmy')
+ .invite();
+
Should you require initialisation logic, then you can overwrite the constructor
in your concern class. A concern's constructor is only given the target class instance as argument.
See booting for additional information.
class Recording extends AbstractConcern {
+ constructor(owner) {
+ super(owner);
+
+ // ...perform your initialisation here...
+ }
+}
+
A concern class may ONLY occur once in a target class' prototype chain. This is a core feature of the concerns mechanism and cannot be circumvented. However, sometimes you may find yourself in situations where different injected concern classes define the same property or method name. When this happens an AliasConflictError
is thrown.
class Label extends AbstractConcern {
+ get name() { /* ...not shown.. */ }
+ set name(v) { /* ...not shown.. */ }
+}
+
+class Category extends AbstractConcern {
+ get name() { /* ...not shown.. */ }
+ set name(v) { /* ...not shown.. */ }
+}
+
+@use(
+ Label,
+ Category // AliasConflictError: Alias "name" for property ...
+)
+class Battery {}
+
To resolve the previous shown naming conflict, you can specify custom "aliases" when injecting a concern class, via an injection configuration object.
// ... Label and Category concerns not shown ...
+
+@use(
+ Label,
+ {
+ concern: Category,
+ aliases: {
+ 'name': 'category' // Alias Category's "name" property as "category"
+ }
+ }
+)
+class Battery {}
+
+const instance = new Battery();
+instance.name = 'AAA';
+instance.category = 'Rechargeable';
+
The aliases
option is key-value record, where;
To prevent a concern class from defining any aliases inside a target class, set the allowAliases
option to false
.
import { getConcernsContainer } from "@aedart/support/concerns";
+
+@use(
+ Label,
+ {
+ concern: Category,
+ allowAliases: false // Category's "name" is NOT aliased in target
+ }
+)
+class Battery {}
+
+const instance = new Battery();
+instance.name = 'AA';
+
+// Interact with Category concern to set "name"
+getConcernsContainer(instance).get(Category).name = 'Rechargeable';
+
You can also use a shorthand version to specify a concern injection configuration, via an array. The first array value must always be the concern class that must be injected. The second value can either be an aliases
object, or boolean value for setting the allowAliases
option.
@use(
+ Label,
+ [Category, {
+ 'name': 'category'
+ }]
+)
+class Battery {}
+
And to prevent a concern from defining aliases in a target:
@use(
+ Label,
+ [Category, false]
+)
+class Battery {}
+
It is not possible to define a property's getter and setter methods in separate concerns, and thereafter use them in a target class. Despite serving different purposes, the getter and setter share the same property name and are therefore treated as being one and the same property key. The following example will therefore always lead to an AliasConflictError
being thrown.
import { AbstractConcern, use } from "@aedart/support/concerns";
+
+class A extends AbstractConcern {
+ get title() { /* ...not shown.. */ }
+}
+
+class B extends AbstractConcern {
+ set title(value) { /* ...not shown.. */ }
+}
+
+@use(
+ A,
+ B // AliasConflictError - "title" property from A!
+)
+class Person {}
+
You can resolve the above shown issue via a custom alias. But, it is advisable to design your concerns such that the offer appropriate getter and setter methods for a property, in one and the same concern - if you intend for such a property to be readable and writable.
The concerns mechanism will never overwrite existing methods or properties inside a target class - not even when those methods or properties are inherited from a parent class.
import { AbstractConcern, use } from "@aedart/support/concerns";
+
+class Connection extends AbstractConcern {
+ driver() {
+ return 'special';
+ }
+}
+
+class Api {
+ driver() {
+ return 'default';
+ }
+}
+
+@user(Connection) // driver() is NOT aliased - method inherited from Api class!
+class SailBoat extends Api {}
+
+const instance = new SailBoat();
+instance.driver(); // default
+
The only way to resolve the above shown issue, is by making use of a custom alias and manually overwrite the inherited method. E.g.
import { AbstractConcern, use } from "@aedart/support/concerns";
+
+class Connection extends AbstractConcern {
+ driver() {
+ return 'special';
+ }
+}
+
+class Api {
+ driver() {
+ return 'default';
+ }
+}
+
+@user(
+ [Connection, {
+ 'driver': 'specialDriver' // alias "driver" as "specialDriver"
+ }]
+)
+class SailBoat extends Api {
+
+ // Overwrite inherited method
+ driver() {
+ // Invoke the "specialDriver"...
+ return this.specialDriver();
+ }
+}
+
+const instance = new SailBoat();
+instance.driver(); // special
+
A concern can use other concerns classes. However, depending on your complexity, doing so may impact performance. Consider the following example:
import { AbstractConcern, use } from "@aedart/support/concerns";
+
+class Ping extends AbstractConcern {
+ ping() {
+ return 'ping';
+ }
+}
+
+@use(Ping)
+class Pong extends AbstractConcern {
+ pong() {
+ return 'pong';
+ }
+}
+
+@use(Pong)
+class Game {}
+
+const instance = new Game();
+
+instance.ping(); // ping
+instance.pong(); // pong
+
In the above shown example, whenever the ping()
method is invoked, the call stack will be similar to the following:
Game (instance).ping() -> Pong (instance).ping() -> Ping (instance).ping()
+
+("->" represents concerns container instance)
+
In some isolated cases, this might be acceptable for you. Nevertheless, if your application makes heavy use of concerns using other concerns, then your application's overall performance could suffer. You should consider merging multiple concern classes into a single class, if it is reasonable and possible. Alternatively, you can also consider extending existing concern classes. For instance:
import { AbstractConcern, use } from "@aedart/support/concerns";
+
+class Ping extends AbstractConcern {
+ ping() {
+ return 'ping';
+ }
+}
+
+// Extend Ping concern...
+class Pong extends Ping {
+ pong() {
+ return 'pong';
+ }
+}
+
+@use(Pong)
+class Game {}
+
+const instance = new Game();
+
+instance.ping(); // ping
+instance.pong(); // pong
+
Now, whenever the ping()
method is invoked, the call stack is slightly reduced:
Game (instance).ping() -> Pong (instance).ping()
+
+("->" represents concerns container instance)
+
Concerns offer a few hook methods. These can be used to perform advanced setup or initialisation logic.
BEFORE
RegistrationTo perform pre-registration logic, use the static BEFORE
method in your concern class. This hook method is invoked before the concern container and aliases are defined in the target class.
The method accepts the following arguments:
target: UsesConcerns
- the target class (class constructor!).import { BEFORE } from "@aedart/contracts/support/concerns";
+import { AbstractConcern } from "@aedart/support/concerns";
+import { isSubclass } from '@aedart/support/reflections';
+
+import { JobHandler } from '@acme/jobs';
+
+class RecordsJobs extends AbstractConcern {
+
+ static [BEFORE](target) {
+ // E.g. prevent this concern from being used by all kinds of targets...
+ if (!isSubclass(target, JobHandler)) {
+ throw new TypeError('RecordsJobs can only be used by JobHandler');
+ }
+ }
+}
+
AFTER
RegistrationTo perform post-registration logic, use the static AFTER
method in your concern class. This method is invoked after the concern container and aliases have been defined in target's prototype.
The method accepts the following arguments:
target: UsesConcerns
- the target class (class constructor!).import { AFTER } from "@aedart/contracts/support/concerns";
+import { AbstractConcern } from "@aedart/support/concerns";
+
+import { ApiConnection } from '@acme/api';
+
+class RecordsJobs extends AbstractConcern {
+
+ static [AFTER](target) {
+ // E.g. init or setup static resources...
+ ApiConnection.init();
+ }
+}
+
Inspired by PHP's Traits, traditional mixins, and a few concepts from dependency injection, the @aedart/support/concerns
submodule offers an alternative approach to reducing some of the limitations of single inheritance.
In this context, a "concern" is a class that can be injected into a target class, by means of the use()
class decorator. The public properties and methods of the concern class are then "aliased" into the target class' prototype. In other words, "proxy" properties and methods are defined in the target class. They forward any interaction to the original properties and methods in the concern class instance.
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+// A concern class...
+class ConsolePrinter extends AbstractConcern {
+ print(message) {
+ console.log(message);
+ }
+}
+
+// Taget class that uses a concern...
+@use(ConsolePrinter)
+class Person {
+
+ sayHi(name) {
+ // Call method in concern
+ this.print(`Hi ${name}`);
+ }
+}
+
+// Later in your application...
+const person = new Person();
+person.sayHi('Atrid'); // Hi Astrid
+
+person.print('Ho ho ho...'); // Ho ho ho...
+
See also "Real" mixins as an alternative.
Most modern IDEs support JSDoc. They can improve your coding experience via code suggestions, highlights and other features. In this chapter, you will find a few ways that you can document your concerns and target class, via JSDoc.
Help wanted!
If you are an expert in JSDoc, then you are most welcome to help improve this chapter. Please see the contribution guide for details on how you can contribute.
@mixin
and @mixes
Possibly the easiest way to document your concern and target class that uses the concern, is via the @mixin
and @mixes
tags.
Downside: Documenting "aliases" (known as virtual fields in the context of JSDoc) is not possible via @mixin
and @mixes
. You can describe an alias via @function
or @member
tag.
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+/**
+ * @mixin
+ * @extends AbstractConcern
+ */
+class Shield extends AbstractConcern {
+
+ /**
+ * Returns the armor level
+ *
+ * @returns {number}
+ */
+ get armor() {
+ return 8;
+ }
+
+ /**
+ * Throw shield towards a target
+ *
+ * @param {object} target
+ *
+ * @returns {number} Damage given to target
+ */
+ throw(target) {
+ // target ignored here...
+ return 3;
+ }
+}
+
+/**
+ * @mixes Shield
+ */
+@use([Shield, {
+ 'throw': 'fight'
+}])
+class Monster {
+
+ /**
+ * Alias for {@link Shield#throw}
+ *
+ * @function fight
+ * @param {object} target The target to throw at...
+ * @return {number} Damage taken by target
+ * @instance
+ * @memberof Monster
+ */
+
+ /**
+ * Do stuff...
+ */
+ do() {
+ this.fight({});
+ }
+}
+
@property
Another possibility is to describe properties and methods available in a target, via the @property
tag. Doing so allows you to immediately describe the "alias" name.
Downside: Properties and methods described via @property
are listed as "static" on the target class. Also, it is not possible to reuse existing JSDoc from your concern.
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+class Armor extends AbstractConcern {
+
+ /**
+ * Returns the armor level
+ *
+ * @returns {number}
+ */
+ get level() {
+ return 8;
+ }
+}
+
+/**
+ * @property {number} armor Returns the armor level
+ */
+@use([Armor, {
+ 'level': 'armor'
+}])
+class Hero {}
+
@borrows
The @borrows
tag does also offer a possible way to describe aliases.
Downside: You are still required to use @member
tag to describe the actual aliases inside your target class.
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+/**
+ * @extends AbstractConcern
+ */
+class Spell extends AbstractConcern {
+
+ /**
+ * Cast the spell
+ *
+ * @name cast
+ *
+ * @returns {number} Damage done
+ */
+ cast() {
+ return 7;
+ }
+}
+
+/**
+ * @borrows Spell#cast as damage
+ */
+@use([Spell, {
+ 'cast': 'damage'
+}])
+class Mage {
+
+ /**
+ * @function damage
+ * @return {number}
+ * @instance
+ * @memberof Npc
+ */
+}
+
@member
Lastly, you can use @member
to describe all aliases directly, without relying on the @borrows
tag.
Downside: This approach can be very cumbersome. Also, reuse of JSDoc is not possible.
import { use, AbstractConcern } from "@aedart/support/concerns";
+
+class Sword extends AbstractConcern {
+
+ /**
+ * Returns amount of damage
+ *
+ * @returns {number}
+ */
+ get slash() {
+ return 3;
+ }
+
+ /**
+ * Returns the sword type
+ *
+ * @name type
+ * @return {string}
+ */
+ get type() {
+ return 'unique';
+ }
+}
+
+
+@use([Sword, {
+ 'slash': 'damage'
+}])
+class Enemy {
+
+ /**
+ * @public
+ * @member {number} damage Alias for {@link Sword#slash}
+ * @memberof Enemy
+ */
+
+ /**
+ * @public
+ * @member {string} type Alias for {@link Sword#type}
+ * @memberof Enemy
+ */
+}
+
At the time of this writing, decorators are still in a proposal phase. To use concerns, you must either use @babel/plugin-proposal-decorators, or use TypeScript 5 decorators.
The class decorator use()
is used to inject one or more concern classes into a target class.
import { use } from "@aedart/support/concerns";
+
+@use(
+ ApiConnection,
+ Serialization,
+ Collections
+)
+class Flight {}
+
When concern classes are injected, the target class is transformed and all concerns are made available inside a private CONCERNS
property. See Manual interaction and Aliases for additional details.
All concerns that are used by a parent class are automatically available (inherited), by child classes.
@use(
+ ApiConnection,
+ Serialization,
+ Collections
+)
+class ApiService {}
+
+class Flight extends ApiService {} // Uses ApiConnection, Serialization, ...etc
+
WARNING
A concern class may ONLY occur once in a target class' prototype chain. An InjectionError
is thrown, if this is violated!
@use(
+ ApiConnection,
+ Serialization,
+ Collections
+)
+class ApiService {}
+
+@use(Serialization) // InjectionError
+class Flight extends ApiService {}
+
See also Conflict Resolution for additional details.
When concerns are injected into a target, they are defined inside a "Concerns Container", which is available in the target instance via the CONCERNS
symbol. Should you require to perform more advanced interaction with a concern class instance, then you can obtain a concern instance via the container's get()
method. It will automatically ensure to boot a concern, if not already booted.
import {
+ use,
+ CONCERNS,
+ AbstractConcern
+} from "@aedart/support/concerns";
+
+class Encryption extends AbstractConcern {
+ encrypt(value) { /* ...not shown... */ }
+}
+
+@use(Encryption)
+class CookieStore {
+ constructor() {
+ const container = this[CONCERNS];
+ const value = container.get(Encryption).encrypt('Lorum lipsum');
+
+ // ...remaining not shown...
+ }
+}
+
You can achieve the same result by using the getContainer()
utility method.
import { use, getContainer } from "@aedart/support/concerns";
+
+// ...Encryption concern not shown...
+
+@use(Encryption)
+class CookieStore {
+ constructor() {
+ const value = getContainer(this)
+ .get(Encryption)
+ .encrypt('Lorum lipsum');
+
+ // ...remaining not shown...
+ }
+}
+
There are 3 ways to obtain the concerns container instance:
A) CONCERNS
symbol
Inside your target class, if you know that concerns are used (if target is a "concern owner"), then you can use the CONCERNS
symbol to gain access to the container.
import { CONCERNS } from "@aedart/support/concerns";
+
+// Inside your target class...
+const container = this[CONCERNS];
+
B) getContainer()
getContainer()
is essentially a just a wrapper for: return this[CONCERNS]
.
import { getContainer } from "@aedart/support/concerns";
+
+// Inside your target class...
+const container = getContainer(this);
+
C) getConcernsContainer()
The getConcernsContainer()
achieves the same result as the previous shown methods. However, it does perform a check of the provided target instance, which ensures that it is a "concern owner". If the target does not pass this test, then a TypeError
is thrown. This might can be useful in situations when you might now know if the target is a concern owner, e.g. when situated in a child class or outside a target class.
import { getConcernsContainer } from "@aedart/support/concerns";
+
+// Inside your target class...
+const container = getConcernsContainer(this);
+
To determine if a target uses one or more concerns, use the usesConcerns()
method. It accepts the following arguments:
instance: object|Owner
- The target class instance....concerns: ConcernConstructor[]
- Concern classes to test for.import {
+ use,
+ AbstractConcern,
+ usesConcerns
+} from "@aedart/support/concerns";
+
+class A extends AbstractConcern {}
+class B extends AbstractConcern {}
+class C extends AbstractConcern {}
+
+@use(
+ A,
+ B
+)
+class Game {}
+
+const instance = new Game();
+
+usesConcerns(instance, A); // true
+usesConcerns(instance, B); // true
+usesConcerns(instance, A, B); // true
+
+usesConcerns(instance, C); // false
+usesConcerns(instance, A, C); // false
+usesConcerns(instance, B, C); // false
+usesConcerns(instance, A, B, C); // false
+
configureCustomError
Configures a custom error by automatically setting the error's name
property to the class' constructor name.
configureCustomError
() accepts the following arguments:
error: Error
- the custom error instancecaptureStackTrace: boolean = false
(optional) Captures and sets error's stack trace¹.¹: See configureStackTrace()
for details.
import { configureCustomError } from "@aedart/support/exceptions";
+
+class MyError extends Error {
+ constructor(message, options) {
+ super(message, options);
+
+ configureCustomError(this);
+ }
+}
+
See Mozilla's documentation on Custom Error Types for additional information.
configureStackTrace
Captures a new stack trace and sets given Error's stack
property.
The function accepts an Error
as argument.
import { configureStackTrace } from "@aedart/support/exceptions";
+
+class MyError extends Error {
+ constructor(message, options) {
+ super(message, options);
+
+ configureStackTrace(this);
+ }
+}
+
WARNING
The stack
is not yet an official feature, even though it's supported by many major browsers and Node.js. If you are working with custom errors, you might not need to capture and set the stack
property. Therefore, you should only use configureStackTrace()
in situations when your JavaScript environment does not support stack traces in custom errors.
AbstractClassError
The AbstractClassError
is intended to be thrown whenever an abstract class is attempted instantiated directly.
import { AbstractClassError } from "@aedart/support/exceptions";
+
+/**
+ * @abstract
+ */
+class MyAbstractClass {
+ constructor() {
+ if (new.target === MyAbstractClass) {
+ throw new AbstractClassError(MyAbstractClass);
+ }
+ }
+}
+
+const instance = new MyAbstractClass(); // AbstractClassError
+
LogicalError
To be thrown whenever there is an error in the programming logic.
Inspired by PHP's LogicException
import { LogicalError } from "@aedart/support/exceptions";
+
+function print(person) {
+ if (printer === undefined) {
+ throw new LogicalError('Printer is missing, unable to print people');
+ }
+}
+
getErrorMessage
Returns an Error's message
, if an Error
instance is provided. Otherwise, a default message is returned.
import { getErrorMessage } from "@aedart/support/exceptions";
+
+try {
+ throw new Error('Something went wrong!');
+} catch(e) {
+ const msg = getErrorMessage(e, 'unknown error'); // Something went wrong!
+}
+
+// ---------------------------------------------------------------------------
+
+try {
+ throw 'Something went wrong!';
+} catch(e) {
+ const msg = getErrorMessage(e, 'unknown error'); // unknown error
+}
+
@aedart/support/exceptions
offers a few utilities for working with Custom Errors.
The support package offers various utilities.
npm install --save-peer @aedart/support
+
yarn add --peer @aedart/support
+
pnpm add --save-peer @aedart/support
+
Provides a decorator that is able to associate metadata with a class, its methods and properties.
import { meta, getMeta } from '@aedart/support/meta';
+
+@meta('service_alias', 'locationSearcher')
+class Service {}
+
+getMeta(Service, 'service_alias'); // locationSearcher
+
Metadata is automatically inherited by subclasses.
import { meta, getMeta } from '@aedart/support/meta';
+
+@meta('service_alias', 'locationSearcher')
+class Service {}
+
+class CitySearcher extends Service {}
+
+getMeta(CitySearcher, 'service_alias'); // locationSearcher
+
You can also overwrite the inherited metadata. The subclass that defines the metadata creates its own copy of the inherited metadata. The parent class' metadata remains untouched.
import { meta, getMeta } from '@aedart/support/meta';
+
+class Service {
+
+ @meta('search.desc', 'Searches for countries')
+ search() {
+ // ...not shown...
+ }
+}
+
+class CitySearcher extends Service {
+
+ @meta('search.desc', 'Searches for cities')
+ search() {
+ // ...not shown...
+ }
+}
+
+const service = new CitySearcher();
+
+getMeta(CitySearcher, 'search.desc'); // Searches for cities
+getMeta(Service, 'search.desc'); // Searches for countries
+
Whenever you read metadata, a copy is returned by the getMeta()
method. This means that you can change the data, in your given context, but the original metadata remains the same.
import { meta, getMeta } from '@aedart/support/meta';
+
+@meta('description', { name: 'Search Service', alias: 'Location Sercher' })
+class Service {}
+
+// Obtain "copy" and change it...
+let desc = getMeta(Service, 'description');
+desc.name = 'Country Searcher';
+
+// Original remains unchanged
+getMeta(Service, 'description').name; // Search Service
+
Caution
Only the meta
decorator is intended to alter existing metadata - even if the value is an object. Please be mindful of this behaviour, whenever you change retrieved metadata using the getMeta()
and getAllMeta()
methods.
At the time of this writing, decorators are still in a proposal phase. To use the meta decorator, you must either use @babel/plugin-proposal-decorators, or use TypeScript 5 decorators.
To define metadata on a class or its elements, use meta()
. It accepts the following arguments:
key
: name of metadata identifier. Can also be a path (see set
).value
: arbitrary data. Can be a primitive value, an object, or a function.To obtain metadata, use the getMeta()
method. You can also use getAllMeta()
, if you wish to obtain all available metadata for a target class.
import { meta } from '@aedart/support/meta';
+
+@meta('service_alias', 'locationSearcher')
+class Service
+{
+ @meta('name', 'Name of service') name;
+
+ @meta('fetch.desc', 'Fetches resource via a gateway')
+ @meta('fetch.dependencies', [ 'my-gateway' ])
+ async fetch(gateway)
+ {
+ // ...implementation not shown...
+ }
+}
+
Use getMeta()
or getAllMeta()
to retrieve metadata.
import { getMeta, getAllMeta } from '@aedart/support/meta';
+
+const service = new Service();
+
+const desc = getMeta(Service, 'fetch.desc');
+const dependencies = getMeta(Service, 'fetch.dependencies');
+
+// Or, obtain all metadata
+const allMeta = getAllMeta(Service);
+
Metadata Availability
Depending on the kind of element that is decorated, metadata might only become available for reading, after a new class instance has been instantiated. This is true for the following elements:
method
getter
setter
field
accessor
Static Elements
If an element is declared as static
, then it's metadata becomes available as soon as the class has been defined.
The getMeta()
method also offers a defaultValue
argument, which is returned, in case that a metadata value does not exist for a given identifier.
const description = getMeta(Service, 'fetch.desc', 'N/A');
+
If you need to create more advanced metadata, you can specify a callback as the first argument for the meta()
decorator method. When using a callback you gain access to the target
that is being decorated, as well as the decorator context
. The callback MUST return an object that contains a key
and a value
property.
import { meta } from '@aedart/support/meta';
+
+class Service {
+
+ @meta((target, context) => {
+ return {
+ key: context.name,
+ value: '...'
+ }
+ })
+ delegateTo(gateway) {
+ // ...not shown...
+ }
+}
+
Although the above example is a bit cumbersome to read, it shows a simple way to defined metadata for a method, which utilises the decorator context
. If you wish, you can use this approach to create your own specialised meta decorators. Doing so can also improve the readability of your class. Consider the following example:
import { meta } from '@aedart/support/meta';
+
+function delegateMeta() {
+ return meta((target, context) => {
+ return {
+ key: context.name,
+ value: '...'
+ }
+ });
+}
+
+class Service {
+
+ @delegateMeta()
+ delegateTo(gateway) {
+ // ...not shown...
+ }
+}
+
The meta
decorator supports the following elements¹:
class
method
getter
setter
field
accessor
¹: An element is determined by the decorator's context.kind
property.
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.
Behind the scene, targetMeta()
uses the meta()
decorator and stores a reference to the target that is decorated inside a WeakMap
.
Unlike the meta()
decorator, targetMeta()
only supports the following elements:
class
method
The following shows how to define target meta for a class and retrieve it.
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
+
The following shows how to define target meta for a class method and retrieve it.
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' ]
+
Target meta is automatically inherited by subclasses and can also be overwritten, similar to that of the meta()
decorator.
Example: classes
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
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' ]
+
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.
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
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
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' ]
+
In relation to the Decorator Metadata proposal, this decorator "mimics" a similar behaviour as the one defined by the proposal. Defining and retrieving metadata relies on a decorator's context.metadata
object, and the Symbol.metadata
property of a class.
Example:
import { meta, getMeta } from '@aedart/support/meta';
+
+@meta('service_alias', 'locationSearcher')
+class Service {}
+
+getMeta(Service, 'service_alias'); // locationSearcher
+
Roughly "desugars" to the following:
function meta(key, value) {
+ return (target, context) => {
+ context.metadata[key] = value;
+ }
+}
+
+@meta('service_alias', 'locationSearcher')
+class Service {}
+
+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
to associate metadata with the intended class. When the Decorator Metadata proposal 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.
descTag
Return the default string description of an object.
import { descTag } from '@aedart/support/misc';
+
+descTag('foo'); // [object String]
+descTag(3); // [object Number]
+descTag([1, 2, 3]); // [object Array]
+descTag(true); // [object Boolean]
+// ... etc
+
The method is a shorthand for the following:
Object.prototype.toString.call(/* your value */);
+
See Mozilla's documentation for additional information.
empty
Determine if value is empty.
See also isset()
.
import { empty } from '@aedart/support/misc';
+
+empty(''); // true
+empty(false); // true
+empty(0); // true
+empty(0n); // true
+empty(NaN); // true
+empty(null); // true
+empty(undefined); // true
+empty([]); // true
+empty({}); // true
+empty(new Set()); // true
+empty(new Map()); // true
+empty(new Int8Array()); // true
+
+empty(' '); // false
+empty('a'); // false
+empty(true); // false
+empty(1); // false
+empty(1n); // false
+empty(-1); // false
+empty(Infinity); // false
+empty([ 1 ]); // false
+empty({ name: 'Jimmy' }); // false
+empty((new Set()).add('a')); // false
+empty((new Map).set('foo', 'bar')); // false
+empty(new Date()); // false
+empty(function() {}); // false
+empty(Symbol('my-symbol')); // false
+
+let typedArr = new Int8Array(1);
+typedArr[0] = 1;
+empty(typedArr); // false
+
@aedart/support/misc
offers miscellaneous utility functions.
isKey
Available since v0.7Determine if given is a valid key or property path identifier.
import { isKey } from '@aedart/support/misc';
+
+isKey('foo'); // true
+isKey(12); // true
+isKey(Symbol('my-symbol')); // true
+isKey([ 'a', 'b.c', Symbol('my-other-symbol')]); // true
+
+isKey(true); // false
+isKey([]); // false
+isKey(null); // false
+isKey(undefined); // false
+isKey(() => true); // false
+
isPrimitive
Determine if a value is a primitive value.
import { isPrimitive } from '@aedart/support/misc';
+
+isPrimitive(null); // true
+isPrimitive(undefined); // true
+isPrimitive(true); // true
+isPrimitive(1); // true
+isPrimitive(1n); // true
+isPrimitive('foo'); // true
+isPrimitive(Symbol('my-symbol')); // true
+
+isPrimitive([1, 2, 3]); // false
+isPrimitive({ name: 'Rian' }); // false
+isPrimitive(function() {}); // false
+
isPropertyKey
Available since v0.7Determine if a key a valid property key name (string, number, or symbol).
import { isPropertyKey } from '@aedart/support/misc';
+
+isPropertyKey('foo'); // true
+isPropertyKey(12); // true
+isPropertyKey(Symbol('my-symbol')); // true
+
+isPropertyKey(true); // false
+isPropertyKey(['a', 'b', 'c']); // false
+isPropertyKey(null); // false
+isPropertyKey(undefined); // false
+isPropertyKey(() => true); // false
+
isset
Determine if value is different from undefined
and null
.
See also empty()
.
import { isset } from '@aedart/support/misc';
+
+isset('foo'); // true
+isset(''); // true
+isset(true); // true
+isset(false); // true
+isset(1234); // true
+isset(1.234); // true
+isset([]); // true
+isset({}); // true
+isset(() => true); // true
+
+isset(undefined); // false
+isset(null); // false
+
You can also determine if multiple values differ from undefined
and null
.
Note: All given values must differ from undefined
and null
, before method returns true
.
isset('foo', { name: 'Jane' }, [ 1, 2, 3 ]); // true
+
+isset('foo', null, [ 1, 2, 3 ]); // false
+isset('foo', { name: 'Jane' }, undefined); // false
+
mergeKeys
Available since v0.7The mergeKeys()
method is able to merge two or more keys into a single key (see isKey()
).
import { mergeKeys } from "@aedart/support/misc";
+
+const key = mergeKeys(Symbol('my-symbol'), [ 'b', 'c.d' ], 23);
+
+console.log(key); // [ Symbol('my-symbol'), 'b', 'c.d', 23 ];
+
toWeakRef
Available since v0.7Wraps a target object into a WeakRef
, if not already instance of a weak reference.
import { toWeakRef } from "@aedart/support/misc";
+
+const person = { name: 'Sine' };
+
+const a = toWeakRef(person); // new WeakRef of "person"
+const b = toWeakRef(a); // same WeakRef instance as "a"
+
+toWeakRef(null); // undefined
+toWeakRef(undefined); // undefined
+
To apply one or more mixins, use the mix()
function and call width()
with the mixins you wish to apply to a superclass.
import { mix } from "@aedart/support/mixins";
+import {
+ RectangleMixin,
+ DescMixin
+} from "@acme/mixins";
+
+class Box extends mix().with(
+ RectangleMixin,
+ DescMixin
+) {
+ // ...remaining not shown...
+}
+
To extend a superclass and apply mixins onto it, pass the superclass as argument for the mix()
function.
class Shape {
+ // ...not shown...
+}
+
+class Box extends mix(Shape).with(
+ RectangleMixin,
+ DescMixin
+) {
+ // ...remaining not shown...
+}
+
Note
By default, if you do not provide mix()
with a superclass, an empty class is automatically created. It is the equivalent of the following:
class Box extends mix(class {}).with(
+ MyMixinA,
+ MyMixinB,
+ MyMixinC,
+) {
+ // ...
+}
+
@aedart/support/mixins
offers an adaptation of Justin Fagnani'smixwith.js
package (originally licensed under Apache License 2.0).
import { mix, Mixin } from "@aedart/support/mixins";
+
+// Define mixin
+const NameMixin = Mixin((superclass) => class extends superclass {
+
+ #name;
+
+ set name(value) {
+ this.#name = value;
+ }
+
+ get name() {
+ return this.#name;
+ }
+});
+
+// Apply mixin...
+class Item extends mix().with(
+ NameMixin
+) {
+ // ...not shown...
+}
+
+// ...Later in your application
+const item = new Item();
+item.name = 'My Item';
+
+console.log(item.name); // My Item
+
See also Concerns as an alternative.
To gain an overview of how inheritance works when applying mixins onto a superclass, consider the following example:
const MyMixin = Mixin((superclass) => class extends superclass {
+ constructor(...args) {
+ super(...args); // Invokes A's constructor
+ }
+
+ // Overwrites A's foo() method
+ foo() {
+ return 'zam';
+ }
+
+ // Overwrites A's bar() method
+ bar() {
+ return super.bar(); // Invoke A's bar() method
+ }
+});
+
+// -------------------------------------------------------------------- //
+
+class A {
+ foo() {
+ return 'foo';
+ }
+
+ bar() {
+ return 'bar';
+ }
+}
+
+// -------------------------------------------------------------------- //
+
+class B extends mix(A).with(
+ MyMixin
+) {
+ constructor(...args) {
+ super(...args); // Invokes MyMixin's constructor
+ }
+
+ // Overwrite MyMixin's foo()
+ foo() {
+ const msg = super.foo(); // Invoke MyMixin's bar() method
+
+ return `<${msg}>`;
+ }
+}
+
+// -------------------------------------------------------------------- //
+
+const instance = new B();
+
+console.log(instance.foo()); // <zam>
+console.log(instance.bar()); // bar
+
instanceof
OperatorWhen you defined your mixins using the Mixin()
decorator function, then it will support instanceof
checks. Consider the following example:
// A regular mixin without "Mixin" decorator
+const MixinA = (superclass) => class extends superclas {
+ // ...not shown...
+};
+
+// Mixin with "Mixin" decorator
+const MixinB = Mixin((superclass) => class extends superclass {
+ // ...not shown...
+});
+
+// -------------------------------------------------------------------- //
+
+class A {}
+
+class B extends mix(A).with(
+ MixinA,
+ MixinB
+) {}
+
+// -------------------------------------------------------------------- //
+
+const instance = new B();
+
+console.log(instance instanceof A); // true
+console.log(instance instanceof B); // true
+console.log(instance instanceof MixinA); // false
+console.log(instance instanceof MixinB); // true
+
You can use the Mixin
decorator to define a new mixin. Amongst other things, the decorator will enable support for instanceof
checks. See instanceof
Operator for additional information.
import { Mixin } from "@aedart/support/mixins";
+
+export const RectangleMixin = Mixin((superclass) => class extends superclass {
+ length = 0
+ width = 0;
+
+ area() {
+ return this.length * this.width;
+ }
+});
+
If you need to perform initialisation logic in your mixins, then you can do so by implementing a class constructor
. When doing so, it is important to invoke the parent constructor via super()
and pass on eventual arguments.
import { Mixin } from "@aedart/support/mixins";
+
+export const RectangleMixin = Mixin((superclass) => class extends superclass {
+
+ constructor(...args) {
+ super(...args); // Invoke parent constructor and pass on arugments!
+
+ // Perform your initialisaiton logic...
+ }
+
+ // ...remaining not shown...
+});
+
forget
Remove (delete) a value in object at given path. Method is an alias for Lodash unset
.
import { forget } from "@aedart/support/objects";
+
+const target = {
+ a: 1234,
+ b: {
+ c: {
+ age: 24
+ }
+ },
+};
+
+forget(target, 'b.c');
+
+console.log(target); // { a: 1234, b: {} }
+
forgetAll
Remove (deletes) all values in object, at given paths.
import { forgetAll } from "@aedart/support/objects";
+
+const target = {
+ a: 1234,
+ b: {
+ c: {
+ age: 24
+ }
+ },
+};
+
+forgetAll(target, [ 'a', 'b.c.age' ]);
+
+console.log(target); // { b: { c: {} } }
+
get
Get value in object at given path. Method is an alias for Lodash get
.
See also set()
.
import { get } from "@aedart/support/objects";
+
+const target = {
+ a: 1234,
+ b: {
+ c: {
+ age: 24
+ }
+ },
+};
+
+let age = get(target, 'b.c.age');
+console.log(age); // 24
+
You can also specify a default value to be returned, if the resolved value is undefined
.
const target = {
+ a: 1234,
+ b: {
+ c: {
+ age: undefined
+ }
+ },
+};
+
+// Returns default value...
+let age = get(target, 'b.c.age', 20);
+console.log(age); // 20
+
has
Determine if path is a property of given object. Method is an alias for Lodash hasIn
.
See also isset()
.
import { has } from "@aedart/support/objects";
+
+const target = {
+ a: 1234,
+ b: {
+ c: {
+ age: 24
+ }
+ },
+};
+
+let result = has(target, 'b.c.age');
+console.log(result); // true
+
hasAll
Determine if all paths are properties of given object.
See also isset()
.
import { hasAll } from "@aedart/support/objects";
+
+const mySymbol = Symbol('my-symbol');
+const target = {
+ a: 1234,
+ b: {
+ name: 'Sven',
+ c: {
+ age: 24,
+ [mySymbol]: true
+ }
+ },
+ d: [
+ { name: 'Jane'},
+ { name: 'Ashley'},
+ ],
+};
+
+const paths = [
+ 'a',
+ 'b.name',
+ 'b.c.age',
+ ['b', 'c', mySymbol],
+ 'd[0]',
+ 'd[1].name',
+];
+
+let result = hasAll(target, paths);
+console.log(result); // true
+
hasAny
Determine if any paths are properties of given object.
import { hasAny } from "@aedart/support/objects";
+
+const target = {
+ a: 1234,
+ b: {
+ name: 'Sven',
+ c: {
+ age: 24
+ }
+ }
+};
+
+const paths = [
+ 'z', // does not exist
+ 'b.c.name', // does not exist
+ 'b.c.age', // exist
+];
+
+let result = hasAny(target, paths);
+console.log(result); // true
+
hasUniqueId
Available since v0.6Determine if an object has a unique id.
See uniqueId
for additional details.
import { hasUniqueId } from "@aedart/support/objects";
+
+const target = {
+ name: 'Ursula'
+};
+
+console.log(hasUniqueId(target)); // false
+
The @aedart/support/objects
submodule offers object related utilities.
isCloneable
Available since v0.9Determines if given object is "cloneable". In this context "cloneable" means that an object implements the Cloneable
interface, and offers a clone()
method.
See @aedart/constracts/support/objects/Cloneable
for details.
import { isCloneable } from "@aedart/support/objects";
+
+class A {};
+
+class B {
+ clone() {
+ return new this();
+ }
+}
+
+isCloneable(null); // false
+isCloneable([]); // false
+isCloneable({}); // false
+isCloneable(new A()); // false
+isCloneable(new B()); // true
+
isPopulatable
Available since v0.9Determines if given object is "populatable". Here, "populatable" means that an object implements the Populatable
interface, and offers a populate()
method.
See @aedart/constracts/support/objects/Populatable
for details.
import { isPopulatable } from "@aedart/support/objects";
+
+class A {};
+
+class B {
+ populate(data) {
+ // ...not shown here...
+
+ return this;
+ }
+}
+
+isPopulatable(null); // false
+isPopulatable([]); // false
+isPopulatable({}); // false
+isPopulatable(new A()); // false
+isPopulatable(new B()); // true
+
isset
Determine if paths are properties of given object and have values. This method differs from has()
, in that it only returns true if properties' values are not undefined
and not null
.
See also misc. isset()
.
import { isset } from "@aedart/support/objects";
+
+const target = {
+ a: 1234,
+ b: {
+ name: undefined,
+ c: {
+ age: null
+ }
+ },
+};
+
+console.log(isset(target, 'a')); // true
+console.log(isset(target, 'b')); // true
+console.log(isset(target, 'b.name')); // false
+console.log(isset(target, 'b.c')); // true
+console.log(isset(target, 'b.c.age')); // false
+
You can also check if multiple paths are set.
console.log(isset(target, 'a', 'b')); // true
+console.log(isset(target, 'b.c', 'b.name')); // false
+console.log(isset(target, 'a', 'b.name', 'b.c.age')); // false
+
merge
Available since v0.9Merges objects recursively into a new object. The properties and values of the source objects are copied, using deep copy techniques, when possible. Behind the scene, most value types are deep copied via structuredClone
.
Example
import { merge } from "@aedart/support/objects";
+
+const person = {
+ 'name': 'Alice',
+};
+
+const address = {
+ 'address': {
+ 'street': 'Somewhere Street 43'
+ },
+};
+
+const result = merge(person, address);
+
+console.log(result);
+
The above shown example results in a new object that looks like this:
{
+ "name": "Alice",
+ "address": {
+ "street": "Somewhere Street 43"
+ }
+}
+
Be default, the following value types are only shallow copied:
const a = {
+ 'foo': null,
+ 'bar': Symbol('my_symbol')
+};
+
+const b = {
+ 'foo': function() {},
+};
+
+const result = merge(a, b);
+
+console.log(result.foo === b.foo); // true
+console.log(result.bar === a.bar); // true
+
Property keys that are considered "unsafe", are never copied.
const a = {
+ 'foo': 'bar'
+};
+const b = {
+ __proto__: { 'is_admin': true }
+}
+
+const result = merge(a, b);
+
+console.log(result); // { 'foo': 'bar' }
+console.log(Reflect.has(result, '__proto__')); // false
+
See isUnsafeKey()
for additional details.
merge()
supports a number of options. To specify thom, use the using()
method.
merge()
+ .using({ /** option: value */ })
+ .of(objA, objB, objC);
+
Note
When invoking merge()
without any arguments, an underlying objects Merger
instance is returned.
depth
The depth
option specifies the maximum merge depth.
512
A MergeError
is thrown, if the maximum depth is exceeded.
const a = {
+ 'person': {
+ 'name': 'Una'
+ }
+};
+
+const b = {
+ 'person': { // Level 0
+ 'age': 24, // Level 1
+ 'address': {
+ 'street': 'Somewhere Str. 654' // Level 2
+ }
+ }
+};
+
+const result = merge()
+ .using({
+ depth: 1
+ })
+ .of(a, b); // MergeError - Maximum merge depth (1) has been exceeded
+
skip
skip
defines property keys that must not be merged.
It accepts an array of property keys or a callback.
const a = {
+ 'person': {
+ 'name': 'Ulrik'
+ }
+};
+
+const b = {
+ 'person': {
+ 'age': 36,
+ 'address': {
+ 'street': 'Nowhere Str. 12'
+ }
+ }
+};
+
+const result = merge()
+ .using({
+ skip: [ 'age' ]
+ })
+ .of(a, b);
+
The above example results in the following new object:
{
+ "person": {
+ "name": "Ulrik",
+ "address": {
+ "street": "Nowhere Str. 12"
+ }
+ }
+}
+
Note
When specifying a list of property keys, then the depth level in which the property key is found does not matter.
You can use a callback, if you need to handle more advanced skip logic. The callback accepts the the following arguments:
key: PropertyKey
- The current property that is being processed.source: object
- The source object that contains the key.result: object
- The resulting object (relative to the current depth that is being processed).The callback MUST return a boolean value; true
if given key must be skipped, false
otherwise.
const a = {
+ 'person': {
+ 'name': 'Jane'
+ }
+};
+
+const b = {
+ 'person': {
+ 'name': 'James',
+ 'address': {
+ 'street': 'Sunview Palace 88'
+ }
+ }
+};
+
+const b = {
+ 'person': {
+ 'name': 'White',
+ }
+};
+
+const result = merge()
+ .using({
+ skip: (key, source, result) => {
+ return key === 'name'
+ && source[key] !== null
+ && !Reflect.has(result, key);
+ }
+ })
+ .of(a, b);
+
The above example results in the following new object:
{
+ "person": {
+ "name": "Jane",
+ "address": {
+ "street": "Sunview Palace 88"
+ }
+ }
+}
+
overwriteWithUndefined
Determines if a property value should be overwritten with undefined
.
Note: By default, all values are overwritten, even when they are undefined
!
const a = { 'foo': true };
+const b = { 'foo': undefined };
+
+merge(a, b); // { 'foo': undefined }
+
+merge()
+ .using({ overwriteWithUndefined: false })
+ .of(a, b) // { 'foo': true }
+
useCloneable
Determines if an object's return value from a clone()
method (see Cloneable
) should be used for merging, rather than the source object itself.
Note: By default, if an object is cloneable, then its return value from clone()
is used.
const a = { 'foo': { 'name': 'John Doe' } };
+const b = { 'foo': {
+ 'name': 'Jane Doe',
+ clone() {
+ return {
+ 'name': 'Rick Doe',
+ 'age': 26
+ }
+ }
+} };
+
+merge(a, b); // { 'foo': { 'name': 'Rick Doe', 'age': 26 } }
+
+merge()
+ .using({ useCloneable: false })
+ .of(a, b); // { 'foo': { 'name': 'Jane Doe', clone() {...} } }
+
mergeArrays
When enabled, arrays, array-like, and concat spreadable objects are merged.
Note: By default, existing array values are NOT merged.
const a = { 'foo': [ 1, 2, 3 ] };
+const b = { 'foo': [ 4, 5, 6 ] };
+
+merge(a, b); // { 'foo': [ 4, 5, 6 ] }
+
+merge()
+ .using({ mergeArrays: true })
+ .of(a, b); // { 'foo': [ 1, 2, 3, 4, 5, 6 ] }
+
Behind the scene, the array merge utility is used for merging arrays.
callback
In situations when you need more advanced merge logic, you may specify a custom callback.
The callback is responsible for returning the value to be merged, from a given source object.
const a = {
+ 'a': 1
+};
+
+const b = {
+ 'b': 2
+};
+
+const result = merge()
+ .using({
+ callback: (target, next, options) => {
+ const { key, value } = target;
+ if (key === 'b') {
+ return value + 1;
+ }
+
+ return value;
+ }
+ })
+ .of(a, b); // { 'a': 1, 'b': 3 }
+
If you do not have other merge options to specify, then you can simply provide a merge callback directly as argument for the using()
method.
const result = merge()
+ .using((target, next, options) => {
+ const { key, value } = target;
+ if (key === 'b') {
+ return value + 1;
+ }
+
+ return value;
+ })
+ .of(a, b);
+
The merge callback is given the following arguments:
target: MergeSourceInfo
- The source target information (see below).next: NextCallback
- Callback to invoke for merging nested objects (next depth level).options: Readonly<MergeOptions>
- The merge options to be applied.target: MergeSourceInfo
The source target information object contains the following properties:
result: object
- The resulting object (relative to object depth)key: PropertyKey
- The target property key in source object to.value: any
- Value of the property in source object.source: object
- The source object that holds the property key and value.sourceIndex: number
- Source object's index (relative to object depth).depth: number
- The current recursion depth.next: NextCallback
The callback to perform the merging of nested objects. It accepts the following arguments:
sources: object[]
- The nested objects to be merged.options: Readonly<MergeOptions>
- The merge options to be applied.nextDepth: number
- The next recursion depth number.For additional information about the merge callback, please review the source code of the defaultMergeCallback()
, inside @aedart/support/objects
.
populate
Available since v0.9The populate()
allows you to populate a target object's properties with those from a source object. The values are shallow copied.
populate()
accepts the following arguments:
target: object
source: object
keys: PropertyKey | PropertyKey[] | SourceKeysCallback = '*'
- The keys to select and copy from source
object. If wildcard (*
) given, then all properties from the source
are selected. If a callback is given, then that callback must return key or keys to select from source
.
safe: boolean = true
- When true
, properties must exist in target (must be defined in target), before they are shallow copied.
Caution
The target
object is mutated by this function.
Note
"Unsafe" properties are disregarded, regardless of what keys
are given.
import { populate } from "@aedart/support/objects";
+
+class Person {
+ name = null;
+ age = null;
+
+ constructor(data) {
+ populate(this, data);
+ }
+}
+
+const instance = new Person({ name: 'Janine', age: 36 });
+instance.name // Janine
+instance.age // 36
+
By default, all keys (*
) from the source
object are attempted populated into the target
. You can limit what properties can be populated, by specifying what keys are allowed to be populated.
class Person {
+ name = null;
+ age = null;
+ phone = null;
+
+ constructor(data) {
+ populate(this, data, [ 'name', 'age' ]);
+ }
+}
+
+const instance = new Person({ name: 'Janine', age: 36, phone: '555 555 555' });
+instance.name // Janine
+instance.age // 36
+instance.phone // null
+
If you need a more advanced way to determine what keys to populate, then you can specify a callback as the keys
argument.
populate(target, source, (source, target) => {
+ if (Reflect.has(source, 'phone') && Reflect.has(target, 'phone')) {
+ return [ 'name', 'age', 'phone' ];
+ }
+
+ return [ 'name', 'age' ];
+});
+
When the safe
argument is set to true
(default behavior), and a property key does not exist in the target
object, then a TypeError
is thrown.
class Person {
+ name = null;
+ age = null;
+
+ constructor(data) {
+ populate(this, data, [ 'name', 'age', 'phone' ]);
+ }
+}
+
+const instance = new Person({
+ name: 'Janine',
+ age: 36,
+ phone: '555 555 555'
+}); // TypeError - phone does not exist in target
+
However, if a requested key does not exist in the source object, then a TypeError
is thrown regardless of the safe
argument value.
class Person {
+ name = null;
+ age = null;
+
+ constructor(data) {
+ populate(this, data, [ 'name', 'age', 'phone' ], false);
+ }
+}
+
+const instance = new Person({
+ name: 'Janine',
+ age: 36
+}); // TypeError - phone does not exist in source
+
set
Set a value in object at given path. Method is an alias for Lodash set
.
import { set } from "@aedart/support/objects";
+
+const target = {};
+
+set(target, 'a.foo', 'bar');
+
+console.log(target); // { a: { foo: 'bar } }
+
uniqueId
Available since v0.6The uniqueId()
is able to return a "unique¹" reference identifier for any given object.
import { uniqueId, hasUniqueId } from "@aedart/support/objects";
+
+const target = {
+ name: 'Ursula'
+};
+
+console.log(uniqueId(target)); // 27
+
+// ...later in your application
+console.log(hasUniqueId(target)); // true
+console.log(uniqueId(target)); // 27
+
The source code is heavily inspired by Nicolas Gehlert's blog post: "Get object reference IDs in JavaScript/TypeScript" (September 28, 2022)
¹: In this context, the returned number is unique in the current session. The number will NOT be unique across multiple sessions, nor guarantee that an object will receive the exact same identifier as in a previous session!
assertHasPrototypeProperty
Available since v0.9Assert that given target object has a prototype
property defined. Throws a TypeError
if target object does not have a prototype
property
See hasPrototypeProperty
for details.
import { assertHasPrototypeProperty } from '@aedart/support/reflections';
+
+assertHasPrototypeProperty({ __proto__: null }); // TypeError
+
classLooksLike
Available since v0.9Determines if a target class "looks like" the provided class "blueprint".
classLooksLike()
accepts the following arguments:
target: object
- the target class object.blueprint: ClassBlueprint
- a blueprint that defines the expected members of a class (see Class Blueprint for details.).import { classLooksLike } from '@aedart/support/reflections';
+
+class A {}
+
+class B {
+ foo() {}
+}
+
+const blueprint = { members: [ 'foo' ] };
+
+classLooksLike(A, blueprint); // false
+classLooksLike(B, blueprint); // true
+
The class "blueprint" is an object that defines the expected members (property keys) of a target class. All defined members must exist in target class' prototype, before the classLooksLike()
returns true
.
You can specify either or both of the following properties in a class blueprint object:
members: PropertyKey[]
- (optional) Properties or methods expected to exist in class' prototype.staticMembers: PropertyKey[]
- (optional) Properties or methods expected to exist in class as static members.Note: If you do not specify either members
or staticMembers
, then a TypeError
is thrown.
class A {
+ foo() {}
+
+ bar() {}
+}
+
+class B {
+ foo() {}
+
+ static bar() {}
+}
+
+const blueprint = { members: [ 'foo' ], staticMembers: [ 'bar' ] };
+
+classLooksLike(A, blueprint); // false
+classLooksLike(B, blueprint); // true
+
classLooksLike()
traverses target class' prototype chain. This means that you can compare a subclass against a blueprint and inherited members will automatically be included in the check.
class A {
+ foo() {}
+}
+
+class B extends A {
+ bar() {}
+}
+
+const blueprint = { members: [ 'foo', 'bar' ]};
+
+classLooksLike(A, blueprint); // false
+classLooksLike(B, blueprint); // true
+
classOwnKeys
Available since v0.9Returns property keys that are defined target class's prototype. It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target classrecursive: boolean = false
- (optional) If true
, then target's prototype chain is traversed and all property keys are returned.import { classOwnKeys } from '@aedart/support/reflections';
+
+class A {
+ foo() {}
+}
+
+class B extends A {
+ get bar() {}
+}
+
+classOwnKeys(B); // [ 'constructor', 'bar' ]
+classOwnKeys(B, true); // [ 'constructor', 'foo', 'bar' ]
+
Caution
classOwnKeys()
throws TypeError
if target does not have a prototype
property.
The classOwnKeys()
function does not return static members of a target class.
getAllParentsOfClass
Available since v0.9Returns all parents of target class. It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target class.includeTarget: boolean = false
- (optional) If true
, then given target is included in the output as the first element.import { getAllParentsOfClass } from '@aedart/support/reflections';
+
+class A {}
+
+class B extends A {}
+
+class C extends B {}
+
+getAllParentsOfClass(C); // [ B, A ]
+getAllParentsOfClass(C, true); // [ C, B, A ]
+
See also getParentOfClass()
.
getClassPropertyDescriptor
Available since v0.9Returns PropertyDescriptor
, from target's prototype that matches given property key.
It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target class.key: PropertyKey
- Name of the property.import { getClassPropertyDescriptor } from '@aedart/support/reflections';
+
+class A {
+ set name(v) {}
+ get name() {}
+}
+
+getClassPropertyDescriptor(A, 'name'); // see "output"...
+
The above show example results in the given output:
const output = {
+ get: function () { /* ...Not shown... */ },
+ set: function (v) { /* ..Not shown... */ },
+ enumerable: false,
+ configurable: true
+};
+
Note
getClassPropertyDescriptor()
returns undefined
if requested key does not exist in class' prototype.
Caution
getClassPropertyDescriptor()
throws TypeError
if target does not have a prototype
property.
getClassPropertyDescriptors
Available since v0.9Returns all property descriptors
that are defined target's prototype.
It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target class.recursive: boolean = false
- (optional) If true
, then target's parent prototypes are traversed. Descriptors are merged, such that the top-most class' descriptors are returned.import { getClassPropertyDescriptors } from '@aedart/support/reflections';
+
+class A {
+ set name(v) {}
+ get name() {}
+ bar() {}
+ [MY_SYMBOL]() {}
+}
+
+getClassPropertyDescriptors(A); // { bar: {...}, name: {...}, [MY_SYMBOL]: {...} }
+
When recursive
is set to true
, then all property descriptors are returned from the target class' prototype chain.
import { getClassPropertyDescriptors } from '@aedart/support/reflections';
+
+class A {
+ set name(v) {}
+ get name() {}
+ foo() {}
+ [MY_SYMBOL]() {}
+}
+
+class B extends A {
+ set bar(v) {}
+ get bar() {}
+}
+
+getClassPropertyDescriptors(B, true);
+// { bar: {...}, foo: {...}, name: {...}, [MY_SYMBOL]: {...} }
+
Caution
getClassPropertyDescriptors()
throws TypeError
if target does not have a prototype
property.
getConstructorName
Available since v0.9Returns target class' constructor name, if available.
It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target classdefaultValue: string|null = null
- (optional) A default string value to return if target has no constructor name.import { getConstructorName } from '@aedart/support/reflections';
+
+class Box {}
+
+getConstructorName(Box); // Box
+getConstructorName(class {}); // null
+getConstructorName(class {}, 'MyBox'); // MyBox
+
getNameOrDesc
Available since v0.9Returns target class' constructor name, or description tag if name is not available.
import { getNameOrDesc } from '@aedart/support/reflections';
+
+class ApiService {}
+
+getNameOrDesc(ApiService); // ApiService
+getNameOrDesc(class {}); // [object Function]
+
getParentOfClass
Available since v0.9Returns the parent class of given target class, or null
if class does not have a parent.
import { getParentOfClass } from '@aedart/support/reflections';
+
+class A {}
+
+class B extends A {}
+
+class C extends B {}
+
+getParentOfClass(A); // null
+getParentOfClass(B); // A
+getParentOfClass(C); // B
+
See also getAllParentsOfClass()
.
hasAllMethods
Available since v0.9Determine if given target object contains all given methods.
It accepts the following arguments:
target: object
- The target....methods: PropertyKey[]
- Names of the methods to check for.import { hasAllMethods } from '@aedart/support/reflections';
+
+const a = {
+ foo: () => { /* ...not shown... */ },
+ bar: () => { /* ...not shown... */ },
+}
+
+hasAllMethods(a, 'foo', 'bar'); // true
+hasAllMethods(a, 'foo', 'bar', 'zar'); // false
+
hasMethod
Available since v0.9Determine if given target object contains method.
It accepts the following arguments:
target: object
- The target.method: PropertyKey
- Name of the method to check for.import { hasMethod } from '@aedart/support/reflections';
+
+const a = {
+ foo: () => { /* ...not shown... */ },
+ bar: () => { /* ...not shown... */ },
+}
+
+hasMethod(a, 'foo'); // true
+hasMethod(a, 'bar'); // true
+hasMethod(a, 'zar'); // false
+
hasPrototypeProperty
Available since v0.9Determines if object has a prototype
property defined and that it is not null
or undefined
.
import { hasPrototypeProperty } from '@aedart/support/reflections';
+
+hasPrototypeProperty(null); // false
+hasPrototypeProperty(Object.create(null)); // false
+hasPrototypeProperty({ __proto__: undefined }); // false
+hasPrototypeProperty({ prototype: null }); // false
+hasPrototypeProperty(() => true); // false
+
+hasPrototypeProperty(Object.create({ prototype: {} })); // true
+hasPrototypeProperty({ __proto__: function() {} }); // true
+hasPrototypeProperty(function() {}); // true
+hasPrototypeProperty(class {}); // true
+
The @aedart/support/reflections
submodule offers a few reflection related utilities.
isConstructor
Based on the TC39 Function.isCallable() / Function.isConstructor()
proposal, the isConstructor()
can determine if given argument is a constructor.
import { isConstructor } from "@aedart/support/reflections";
+
+isConstructor(null); // false
+isConstructor({}); // false
+isConstructor([]); // false
+isConstructor(function() {}); // true
+isConstructor(() => {}); // false
+isConstructor(Array); // true
+isConstructor(class {}); // true
+
Acknowledgement
The source code of the above shown methods is heavily inspired by Denis Pushkarev's Core-js implementation of the Function.isCallable / Function.isConstructor proposal (License MIT).
isKeySafe
Available since v0.9Opposite of isKeyUnsafe()
.
import { isKeySafe } from '@aedart/support/reflections';
+
+isKeySafe('name'); // true
+isKeySafe('length'); // true
+isKeySafe('constructor'); // true
+isKeySafe('__proto__'); // false
+
isKeyUnsafe
Available since v0.9Determines if a property key is considered "unsafe".
import { isKeyUnsafe } from '@aedart/support/reflections';
+
+isKeyUnsafe('name'); // false
+isKeyUnsafe('length'); // false
+isKeyUnsafe('constructor'); // false
+isKeyUnsafe('__proto__'); // true
+
Note
Behind the scene, the isKeyUnsafe()
function matches the given key against values from the predefined DANGEROUS_PROPERTIES
list, which is defined in the @aedart/contracts/support/objects
submodule;
import { DANGEROUS_PROPERTIES } from "@aedart/contracts/support/objects";
+
isSubclass
Available since v0.9Determine if target class is a subclass (child class) of given superclass (parent class).
It accepts the following arguments:
target: object
- The target.superclass: ConstructorOrAbstractConstructor
- The superclass.import { isSubclass } from '@aedart/support/reflections';
+
+class A {}
+
+class B extends A {}
+
+isSubclass({}, A); // false
+isSubclass(A, A); // false
+isSubclass(A, B); // false
+
+isSubclass(B, A); // true
+
isSubclassOrLooksLike
Available since v0.9Determine if target class is a subclass of given superclass, or if it looks like given blueprint.
It accepts the following arguments:
target: object
- The target.superclass: ConstructorOrAbstractConstructor
- The superclass.blueprint: ClassBlueprint
- Class Blueprint (See classLooksLike
).import { isSubclassOrLooksLike } from '@aedart/support/reflections';
+
+class A {
+ foo() {}
+}
+class B extends A {}
+
+class C {
+ foo() {}
+}
+
+isSubclassOrLooksLike(B, A, { members: [] }); // true
+isSubclassOrLooksLike(C, A, { members: [] }); // false
+isSubclassOrLooksLike(C, A, { members: [ 'foo' ] }); // true
+
See isSubclass()
and classLooksLike()
for additional details.
isWeakKind
Available since v0.9Determine if object of a "weak" kind, e.g. WeakRef
, WeakMap
, or WeakSet
.
import { isWeakKind } from '@aedart/support/reflections';
+
+const a = {};
+
+isWeakKind(null); // false
+isWeakKind(a); // false
+isWeakKind(new Map()); // false
+isWeakKind(new Set()); // false
+
+isWeakKind(new WeakRef(a)); // true
+isWeakKind(new WeakMap()); // true
+isWeakKind(new WeakSet()); // true
+
The <VersionDisclaimer />
component is a simply "notice" container, which can be used in your layout. Most often, you would use this to display a custom message when outdated / unsupported documentation is being viewed.
<VersionDisclaimer type="warning" label="Note">
+ You are viewing documentation for an unsupported version...
+</VersionDisclaimer>
+
type
(optional)The type
property accepts the following values:
info
(default)warning
danger
label
(optional)An optional label that is used as a prefix for the custom disclaim message.
The following example assumes that you are using an Archive
component to structure documentation. When doing so, you can display a custom message whenever "outdated" or "upcoming" documentation is being viewed.
To achieve this, you will need to create a custom layout (e.g. extend the default theme). Create a new layout, e.g. in .vuepress/layouts/Layout.vue
.
<script setup lang="ts">
+import ParentLayout from '@vuepress/theme-default/layouts/Layout.vue';
+import VersionDisclaimer from "@aedart/vuepress-utils/components/VersionDisclaimer.vue";
+import {usePageData} from "@vuepress/client";
+import {isViewingNextRef, isViewingOtherRef} from "@aedart/vuepress-utils";
+import archive from "../my_archive";
+
+const page = usePageData();
+const showForNext = isViewingNextRef(page, archive);
+const showForOther = isViewingOtherRef(page, archive);
+</script>
+
+<template>
+ <ParentLayout>
+ <template #page-top>
+
+ <VersionDisclaimer v-if="showForNext">
+ You are viewing documentation for next version...
+ </VersionDisclaimer>
+
+ <VersionDisclaimer v-if="showForOther" type="danger" label="Oh oh">
+ You are viewing old stuff...
+ </VersionDisclaimer>
+
+ </template>
+ </ParentLayout>
+</template>
+
The isViewingNextRef()
method returns a computed property that indicates if visitor is viewing the "next" collection of pages. The isViewingOtherRef()
methods returns a computed property that determines if pages are viewed that do not belong to "next" nor "current" collections.
In your Client Config File, use the custom Layout
.
import { defineClientConfig } from '@vuepress/client';
+import Layout from "./layouts/Layout.vue";
+
+export default defineClientConfig({
+ layouts: {
+ Layout
+ }
+});
+
Contains a few utilities for Vuepress v2, which are also used for building this documentation site. Amongst them are:
npm install --save-dev @aedart/vuepress-utils
+
yarn add --dev @aedart/vuepress-utils
+
pnpm add --save-dev @aedart/vuepress-utils
+
The Archive
component is a helper that keeps track of collections of pages in an "archive" (exactly like this site). It operates on the notion that there is always a "current" and "next" collection of pages. It can be used to structure documentation for various versions of your application, components, packages...etc.
The following illustrates a possible archive structure of your documentation. Notice the "current" and "next" directories. These two directories are essential for the Archive
component. Their names can be configured (shown later). Each of the directories contains a collection of pages.
/.vuepress
+ /my_archive
+ index.ts
+ v3x.ts
+ v4x.ts
+ v5x.ts
+ ...
+ client.ts
+ config.ts
+
+/my_archive
+ /current
+ README.md
+ ...
+ /next
+ README.md
+ ...
+ /v4x
+ README.md
+ ...
+ /v3x
+ README.md
+ ...
+ README.md
+
Each Archive
component is dependent on having its structure defined by means of PagesCollection
components. As such, to represent the "current" collection, you must create a new PagesCollection
instance. Use the static make()
method to create a new instance. It accepts 3 arguments:
name: string
Name or title of the collection.path: string
The relative path in the archive to the collection.pages: SidebarConfigArray = []
An array of pages or group of pages. Each page's path is relative to the collection's path.// E.g. inside /.vuepress/my_archive/v5x.ts
+import {PagesCollection} from "@aedart/vuepress-utils/navigation";
+
+export default PagesCollection.make('v5.x', '/v5x', [
+ {
+ text: 'Version 5.x',
+ collapsible: true,
+ children: [
+ '',
+ 'contribution-guide',
+ 'security',
+ 'code-of-conduct',
+ ]
+ },
+ {
+ text: 'Packages',
+ collapsible: true,
+ children: [
+ 'packages/',
+
+ // ...remaining not shown here...
+ ]
+ },
+
+ // ...etc
+]);
+
PagesCollection `path`
The path
argument of a pages collection will automatically be changed, by the Archive
component, if the collection is marked as the "current" or "next" collection (covered in next section).
Once you have your "current" and "next" collections defined, you can create a new Archive
instance. Use the static make()
method to create a new instance. It accepts 3 arguments:
current: PagesCollection
The collection to be marked as the "current".next: PagesCollection
The collection to be marked as the "next".collections: PagesCollection[] = []
Array of all available collections, including "next" and "current".// E.g. inside /.vuepress/my_archive/index.ts
+import {PagesCollection} from "@aedart/vuepress-utils/contracts";
+import {Archive} from "@aedart/vuepress-utils/navigation";
+import v3x from "./v3x.ts";
+import v4x from "./v4x.ts";
+import v5x from "./v5x.ts";
+import v6x from "./v6x.ts";
+
+// Defined the "current" colelction
+const CURRENT: PagesCollection = v5x;
+
+// Defined the "next" colelction
+const NEXT: PagesCollection = v6x;
+
+// Define all collections... next and current should also be part of this...
+const ALL: PagesCollection[] = [
+ NEXT,
+ CURRENT,
+ v4x,
+ v3x,
+ // ... etc
+];
+
+// Finally, create and export your archive with "current" and "next"
+const archive = Archive.make(CURRENT, NEXT, ALL);
+archive.path = '/my_archive';
+
+export default archive;
+
As shown in the previous example, the archive's path was set to /my_archive
by explicitly setting the path
property. You can do the same for its name:
// ...previous not shown ...
+const archive = Archive.make(CURRENT, NEXT, ALL);
+archive.name = 'Good old stuff';
+archive.path = '/old_stuff';
+
WARNING
Your archive's directory structure must match the specified path
or vuepress will not be able to find it and display a "404 Not Found".
/old_stuff
+ /current
+ README.md
+ ...
+ /next
+ README.md
+ ...
+ ...
+
Whenever a collection is marked as "current" or "next", its path
is automatically changed to /current
or /next
. This means that the full path of those collections will be the archive's path
+ current or next, e.g.
/archive/current
/archive/next
To change these paths, specify the currentPath
and nextPath
properties in your Archive
instance.
archive.currentPath = '/live'
+archive.nextPath = '/upcoming'
+
WARNING
When you change the "current" and "next" paths in your archive, then the directory structure MUST reflect these names. From the above example, the archive's directory structure should now be the following:
/my_archive
+ /live
+ README.md
+ ...
+ /upcoming
+ README.md
+ ...
+ ...
+
You may also change the labels for "current" and "next", in a similar way as for changing their paths.
archive.currentLabel = 'Live'
+archive.nextLabel = 'What\'s Next?'
+
To put it all together, in your Config File, import your archive instance. Inside your theme
settings, you can create a dropdown representation of your archive, by invoking the asNavigationItem()
method. A sidebar configuration can be created via the sidebarConfiguration()
method.
import {defineUserConfig} from 'vuepress';
+import defaultTheme from "@vuepress/theme-default"
+import archive from './my_archive'
+
+export default defineUserConfig({
+
+ // ...other settings not shown...
+
+ theme: defaultTheme({
+
+ // ... other theme settings not shown ...
+
+ navbar: [
+ archive.asNavigationItem(),
+ ],
+
+ sidebar: archive.sidebarConfiguration()
+ }),
+});
+
Use can review the source code and configuration of this site, as a complete example of how the Archive
component can be used.
lastUpdatedPlugin()
allows you to specify a custom datetime format for the "last updated" date, for the default theme. It uses Luxon to perform the formatting.
In your Config File, add the lastUpdatedPlugin()
:
import {defineUserConfig} from 'vuepress';
+import {lastUpdatedPlugin} from "@aedart/vuepress-utils/plugins";
+
+export default defineUserConfig({
+
+ // ...other settings not shown...
+
+ plugins: [
+
+ lastUpdatedPlugin()
+ ]
+});
+
Use the format
argument to specify your desired datetime format. See Luxon documentation for available format tokens.
lastUpdatedPlugin({ format: 'dd-MM-yyyy HH:mm:ss' })
+
Note
The plugin uses yyyy-MM-dd HH:mm:ss ZZZZ
as default format, when none is given.
Example output: 2023-03-19 16:09:20 GMT+1
@aedart/xyz
is an internal package that is used for experiments, e.g. tinkering with decorators, proxies, or whatever might be "fun" to try out. The package is NOT published, nor is it intended to ever be published.
WARNING
DO NOT DISCLOSE SECURITY RELATED ISSUES PUBLICLY!PLEASE SEND AN ENCRYPTED EMAIL TO ME INSTEAD!
See "How to report a vulnerability" for instructions.
If you have discovered a vulnerability, please send an encrypted email to Alin Eugen Deac (aedart@gmail.com). Use the public PGP key listed below for encryption. Your email will be prioritised and addressed as quickly as possible.
In addition, please make sure that the contents of your email contains appropriate information about the vulnerability. E.g.:
How to encrypt emails using PGP
If you do not know how to use PGP encryption, then the following can help you
-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: Mailvelope v4.4.1
+Comment: https://www.mailvelope.com
+
+xsFNBGISJyIBEAC6JEo+xr48WA6fDTVjJKuF3MaT91VBrjynKNxcCBHINLw8
+qIiJYfRFvny0Ffx8xuKlK1rVJ4BpsX4fURb5mFiQtFe6paSBVLUOCDyjlhdz
+srKwhtz4tDpzc94dCUWkNJgWNa4ah5TuEJZLdXGptYeUtj3/UNqmAxsx6NEj
+/xXLX3eZ+rwn88UVxKoTFge1AwCLmRk0N55s6g1tQJ8MrGZvjjJ0OGCnDKhy
+h3CZI2SqlovL4/MXq0cYexqyUDLcZzEgbAKvNA/vnxc9oIBEIZoRZ1QxqXO6
+Z/Da8ItUJ8Eg8MmWIFmnqs+YfeOcHqxflm288c44BRPN3e3bM9tb8qhi+Xd8
+SE3D0Az1QVq3aoKzvF35bnV0KwB86so8ud4/fVtfBq6kklS6ZSS3wNhCRG3f
+imenk2DvOni9MqriXPOKxVBqa9yC0otua7/IoZGksNOwzjdUwpfY3guTv4iu
+nUcieHBmXtX57N4PHx1DZAWi0Zyh0dop3cnV8my3ZdQ0fR+GSI49z+1gcpSE
+OgANIDb5Ejk3EalmYxv4OKWl9M3sztaP5q7dOAo5vYoTwFv9cnjXaj7+8F7b
+fH0rQi/xJWeHd7BKHzgwz3zP/Wz57wFNvW9Yg0HVjjkBB/fwmqIGtImkVICV
+JfhnWBApezI+m8W4GI777GtG+DhR3nsTJQ43DQARAQABzSJBbGluIEV1Z2Vu
+IERlYWMgPGFlZGFydEBnbWFpbC5jb20+wsF1BBABCAAfBQJiEiciBgsJBwgD
+AgQVCAoCAxYCAQIZAQIbAwIeAQAKCRBcsQfpwXzHeO1kEACHLk3KrWzJ3qqP
+RP4RERnaD3wXy7j2my4zuAEDPq4miEekSfSSgna0yca5L22scDcJYk18wOlX
+f3TprZVkQ0xKKukHMgaJY3ydpug1dEM1Ve9TxzDVUEqiOAxg55P0s5rA8Uec
+exW8lzQcfCnR4ascuPCKoxo/zbvVoIZr3tnKaQirE+DJFcbsJlqNROG/XV+f
+mwjpI2/LIi+qJ/qxmL8iqE4KqUI147Usk1wztQGc6aY0OfK7uxZ+s0YvkbFQ
+lBJoJLG0MVodq9fpiakEzWN0q1OoQEJALrm1OvZD4JtxGSeSVw973y33nBdg
+NDbbwXmeh4mu6ObZgmNImxt/nTLBWsqYTS/3mzyUC/3eimuYWIsSi7A8JJ/s
+AmcG+Wp7yM9rRbiuZG/m0C+HneAICBcJ4QDV84XY4bMgu2zMgyWM5dbd65cR
+7ug+p8z7g7CieH+H1OYXNApn8+7HZjw7CbMsUaAGk4IgndCb1kxxI8+q1Et4
+waR0mMJ6uOslbP+24uEwrIe2boW4mcssqlNBSfBR3OCWs0VWNvB1tq1NBUZK
+rMuqdPzL/eNtObuNcEndXz18VWWmJBJaGCC7r5ARXB+MzO07B5UTHCu1mXCR
+6t9gqLuSEOwpbK8BJHSuyxYAPPlc7X7BUSlyZVmEqiVgmtHM0F6E4pydR7e4
+uSiBOG7d0c7BTQRiEiciARAAzR2TnBvCNI3/3bl6ZOPtru0Yr++gjIsNYsNc
+61wHDy9mhRENi1AjYngJK4UEmDrm/3rxlIrZrVm2OOXTLFxveGWJozeR1ovk
+og3zojZyYOgr8AabEtRAbsEHwIfjCSXV/z0c4mp05Jtcztll0OM+NE962gRI
+gt7tDzJweWKdsHOF4agQ3+L4g+nD76ZFLnpjoZsfSekdJLtCfl4bYVQgi4/I
+FFpJM3o7CiHeEjm26eaIu5SQ/U2Ciwnepgp7WDq+J9pbQS/5pq8wjSqUO0wW
+zyrD4fEjx2TnBTzV7aea4OumKZB6X+lJwTTpvN7BVF2ODfL2IEohzk14p3kM
+RMSpNgGtFeg7lfFDW9j0zK6vHOkFSDUZ0VbWpj6K6FtsimKx92bSS046Bdu4
+7l8Vx33vGk/QL09YiEJkeqE39DJ28TShyw1mtfUQXAJykaPIO9bpImTgYjwu
+umD9y7v3Ubr+9g0Bvsy7byEsppDtcFCuYK1wtd6kvdFZSCEAJIc23JGPwVLJ
+Mo7gDgR8W30TBuvhi2hEuwQq1kgE2XgrYMb4BbIo5OTplRqKePvEyq+Xgn1K
+Kak4HErs0X6uFcCXynAGINIV2H7pVCtCYC0XtaoG6WvtC6NR1TWGpmhVchY0
+3jCQdBpwvQyuTw4DcSJcbhFmJzo9PdzRN8fu8M4Kh9LapvUAEQEAAcLBXwQY
+AQgACQUCYhInIgIbDAAKCRBcsQfpwXzHeEmKD/4mysrPW/t9BfDE4kWYBCHO
+l54lXV9FfC6xHAR0v9qJTurv87svOZBwWeL8lnyFELB9DCLjgOtNT9ZbUcdm
+HuesBTm9jV83K3uvMBqzPKvxX1IZnxhVsg+KSRocY3PMa2QPCyriKDZ7tsFe
+5yMeXqHOtHuDsVzYJhmCK9oz85tl5xozFVJgmapn007ozAoK6XsQQmjY8U+C
+jiL/WHdN9yuFxYnxwCdtJ7Nh72uixs2nRZKjcCzWAFYHo6iJuWbxWG2l4LIq
+Rh9x3pLDSCth4xigObAaGiIj4nMWtrX9Re8/lze3BFKfB0IDp0+KUl63afeI
+/xcOoJ5JkhFqe/AJVX203Gr6niqn3ckD08zeB3ZgMgTVTo2uwX03wM9AHz43
+0Sp5n1c321E6mWtrBPTDL8xdBccLETYcmpDKA6jHUepwQTxst3ytbv/G1nZ5
+Dj6ADe/CSC95O6CO5BeXkM3gZeMmkuHtxjue1MLLhnGgrp1HMjxp5L92tUMU
+6chem/yKqEE5Ac0TCNMDAs+soQ/ISN++NQtk1f5QEDE2F7Ji8eLAFSclnXbh
+lf1Hu4IaSrvr8q+ctAGIgrh/N4oWDm/jYPT+QPCQEaYKUuGBNHIWWU5Pb+9S
+1IIhuIP3hQAzawPbIeoEgt2lCIii3BjLcZAZ2cVv9KuCG09D7F86j5Dz/R7V
+gA==
+=E13G
+-----END PGP PUBLIC KEY BLOCK-----
+
Please read the Support Policy for additional information about what versions are supported.
The following deprecated types have been removed (deprecated in version v0.7.0
):
ClassContext
MethodContext
GetterContext
SetterContext
FieldContext
AccessorContext
MetadataContext
MemberContext
More information available in the source code and CHANGELOG.md
targetMeta()
and inheritTargetMeta()
)The util functions targetMeta()
and inheritTargetMeta()
now throw a MetaError
instead of previous TypeError
. If you rely on TypeError
as the type of exception being thrown in a try-catch statement, when decorating class members, then you should change it to MetaError
.
Ion now requires Node.js v20.11.0
or greater.
Various metadata related type definitions have now been deprecated in favour of TypeScript's own definitions. Mostly, this should not affect the implementation. However, if your decorator(s) depend on the following types (see below), then you are strongly encouraged to use corresponding TypeScript defined types instead.
Deprecated types and interfaces are defined in @aedart/contracts/support/meta
:
ClassContext
MethodContext
GetterContext
SetterContext
FieldContext
AccessorContext
MetadataContext
MemberContext
More information available in the source code and CHANGELOG.md
The @aedart/vuepress-utils
has been upgraded to use vuepress v2.0.0-rc.2
, meaning that you no longer should require to manually define your vuepress
dependency, in your application's packages.json
file.
❌ Previously
{
+ "devDependencies": {
+ "@aedart/vuepress-utils": "^0.6.1",
+ "vuepress": "2.0.0-beta.61",
+ "@vuepress/core": "2.0.0-beta.61",
+ "@vuepress/utils": "2.0.0-beta.61",
+ "@vuepress/client": "2.0.0-beta.61"
+ }
+}
+
✔️ Now
{
+ "devDependencies": {
+ "@aedart/vuepress-utils": "^0.7.0"
+ }
+}
+
Please read vuepress' changelog for additional details.
Webpack Bundle
In addition to the above, the @aedart/vuepress-utils
automatically comes with @vuepress/bundler-webpack
as its peed dependency.
forgetAll()
, hasAll()
and hasAny()
forgetAll()
, hasAll()
and hasAny()
(in @aedart/support/object
submodule) now accept rest parameters instead of an array of paths. If you are using these methods, then you need to upgrade or risk unexpected results.
❌ Previously
import {
+ forgetAll,
+ hasAll,
+ hasAny
+} from "@aedart/support/objects";
+
+hasAny(target, [ 'a', 'b.c.age' ]);
+hasAll(target, [ 'a', 'b.c.age' ]);
+forgetAll(target, [ 'a', 'b.c.age' ]);
+
✔️ Now
hasAny(target, ...[ 'a', 'b.c.age' ]);
+hasAll(target, ...[ 'a', 'b.c.age' ]);
+forgetAll(target, ...[ 'a', 'b.c.age' ]);
+
+// ...Or
+hasAny(target, 'a', 'b.c.age');
+hasAll(target, 'a', 'b.c.age');
+forgetAll(target, 'a', 'b.c.age');
+
More details can be found in the changelog.
The next version of Ion has yet to be designed and implemented. Come back at a later time to review the documentation...
Consider the following example:
import { use, AbstractConcern } from "@aedart/support/concerns";\n\nclass Levels extends AbstractConcern {\n get level() { /* ...not shown */ }\n set level(value) { /* ...not shown */ }\n clear() { /* ...not shown */ }\n}\n\n@use(Levels)\nclass Recorder {}\n
The aliasing mechanism will transform the target class into something that very roughly corresponds to this:
import {\n use,\n CONCERNS,\n AbstractConcern\n} from "@aedart/support/concerns";\n\nclass Levels extends AbstractConcern {\n get level() { /* ...not shown */ }\n set level(value) { /* ...not shown */ }\n clear(level) { /* ...not shown */ }\n}\n\nclass Recorder {\n // ...private concerns container not shown...\n\n // get level "alias"\n get level() {\n return this[CONCERNS].get(Levels)['level'];\n }\n\n // set level "alias"\n set level(value) {\n this[CONCERNS].get(Levels)['level'] = value;\n }\n\n // method clear "alias"\n clear(...args) {\n return this[CONCERNS].get(Levels)['clear'](...args);\n }\n}\n
When a property or method from a concern already exists in the target class' prototype chain¹, then NO Alias is defined. Said differently, the use()
class decorator does NOT overwrite a target class' properties or methods.
class Label extends AbstractConcern {\n get name() { /* ...not shown.. */ }\n set name(v) { /* ...not shown.. */ }\n}\n\n@use(Label) // Label's "name" property is NOT aliased\nclass Battery {\n\n // Battery's get/set "name" remains untouched by concern\n get name() { /* ...not shown.. */ }\n set name(v) { /* ...not shown.. */ }\n}\n
¹: Inherited properties and methods are also respected.
',4),d={};var k=(0,a(6262).A)(d,[["render",function(n,s){const a=(0,e.g2)("router-link"),d=(0,e.g2)("RouteLink");return(0,e.uX)(),(0,e.CE)("div",null,[t,p,(0,e.Lk)("nav",o,[(0,e.Lk)("ul",null,[(0,e.Lk)("li",null,[(0,e.bF)(a,{to:"#properties-methods"},{default:(0,e.k6)((()=>[(0,e.eW)("Properties & Methods")])),_:1})]),(0,e.Lk)("li",null,[(0,e.bF)(a,{to:"#if-property-or-method-already-exists"},{default:(0,e.k6)((()=>[(0,e.eW)("If property or method already exists")])),_:1})])])]),c,(0,e.Lk)("p",null,[(0,e.eW)('When injecting a concern into a target class, the concern\'s public properties and methods are defined as "aliases" ('),l,(0,e.eW)("), in the target class' prototype ("),(0,e.Lk)("em",null,[(0,e.eW)("see "),(0,e.bF)(d,{to:"/archive/current/packages/support/concerns/concernClass.html#customise-alias-members"},{default:(0,e.k6)((()=>[i,(0,e.eW)(" symbol")])),_:1}),(0,e.eW)(" for additional details")]),(0,e.eW)(").")]),u,(0,e.Lk)("p",null,[(0,e.eW)("See "),(0,e.bF)(d,{to:"/archive/current/packages/support/concerns/usage.html#manual-interaction"},{default:(0,e.k6)((()=>[(0,e.eW)("Manual interaction")])),_:1}),(0,e.eW)(" and "),(0,e.bF)(d,{to:"/archive/current/packages/support/concerns/conflictResolution.html"},{default:(0,e.k6)((()=>[(0,e.eW)("Conflict Resolution")])),_:1}),(0,e.eW)(" for additional details.")]),r,(0,e.Lk)("p",null,[(0,e.eW)("See "),(0,e.bF)(d,{to:"/archive/current/packages/support/concerns/conflictResolution.html"},{default:(0,e.k6)((()=>[(0,e.eW)("Conflict Resolution")])),_:1}),(0,e.eW)(" for additional details.")])])}]]);const m=JSON.parse('{"path":"/archive/current/packages/support/concerns/aliases.html","title":"Aliases","lang":"en-GB","frontmatter":{"title":"Aliases","description":"What are aliases","sidebarDepth":0},"headers":[{"level":2,"title":"Properties & Methods","slug":"properties-methods","link":"#properties-methods","children":[]},{"level":2,"title":"If property or method already exists","slug":"if-property-or-method-already-exists","link":"#if-property-or-method-already-exists","children":[]}],"git":{"updatedTime":1709641376000,"contributors":[{"name":"alin","email":"alin@rspsystems.com","commits":1}]},"filePathRelative":"archive/current/packages/support/concerns/aliases.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')}}]); \ No newline at end of file diff --git a/assets/js/app.8f7791e3.js b/assets/js/app.8f7791e3.js new file mode 100644 index 00000000..b1ca44ed --- /dev/null +++ b/assets/js/app.8f7791e3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[3524],{2090:function(e,t,r){r.d(t,{B:function(){return I}});var a=r(6358),s=r(3825),i=r(3450),l=r(5436),c=r(1599),n=r(5496),o=r(443),p=r(355),u=r(8973),h=r(641),d=r(953),g=r(3567),m=r(33),k=(0,h.pM)({name:"VersionDisclaimer",props:{type:{type:String,default:"info"},label:{type:String}},setup(e){const t=e.type,r=e.label;return{css:(0,h.EW)((()=>({"version-disclaimer":!0,[t]:!0}))),label:(0,h.EW)((()=>r||t.charAt(0).toUpperCase()+t.slice(1))),type:t}}}),v=(0,r(6262).A)(k,[["render",function(e,t,r,a,s,i){return(0,h.uX)(),(0,h.CE)("div",{class:(0,m.C4)(e.css)},[(0,h.Lk)("label",null,(0,m.v_)(e.label),1),(0,h.RG)(e.$slots,"default")],2)}]]),b=r(3456);function f(e,t){return t.startsWith("/")||(t="/"+t),(e+t).replaceAll("//","/")}function x(e,t){const r=t.nextFullPath;return e.value.path.startsWith(r)}class y{_name;_path;_archive=null;pages;constructor(e,t,r=[]){this._name=e,this._path=t,this.pages=r}static make(e,t,r=[]){return new this(e,t,r)}set name(e){this._name=e}get name(){return this._name}set path(e){this._path=e}get path(){return this._path}get fullPath(){return this.prefixWithArchivePath(this.path)}set archive(e){this._archive=e}get archive(){return this._archive}asNavigationItem(){return{text:this.name,link:this.fullPath}}asSidebarObject(){return{[this.fullPath]:this.sidebar()}}sidebar(){return this.resolvePages(this.pages)}resolvePages(e){return e.forEach(((e,t,r)=>{r[t]=this.resolvePage(e)})),e}resolvePage(e){return"string"==typeof e?this.prefixWillFullPath(e):Reflect.has(e,"children")&&0!==e.children.length?(e.children.forEach(((e,t,r)=>{r[t]=this.resolvePage(e)})),e):e}prefixWillFullPath(e){return f(this.fullPath,e)}prefixWithArchivePath(e){return f(null!==this.archive?this.archive.path:"",e)}}const L=y.make("v0.x","/v0x",[{text:"Version 0.x",collapsible:!0,children:["","upgrade-guide","contribution-guide","security","code-of-conduct","origin"]},{text:"Packages",collapsible:!0,children:["packages/",{text:"Contracts",collapsible:!0,children:["packages/contracts/","packages/contracts/install"]},{text:"Support",collapsible:!0,children:["packages/support/","packages/support/install",{text:"Arrays",collapsible:!0,children:["packages/support/arrays/","packages/support/arrays/includesAll","packages/support/arrays/includesAny","packages/support/arrays/isArrayLike","packages/support/arrays/isConcatSpreadable","packages/support/arrays/isSafeArrayLike","packages/support/arrays/isTypedArray","packages/support/arrays/merge"]},{text:"Concerns",collapsible:!0,children:["packages/support/concerns/","packages/support/concerns/prerequisites","packages/support/concerns/concernClass","packages/support/concerns/usage","packages/support/concerns/aliases","packages/support/concerns/conflictResolution","packages/support/concerns/booting","packages/support/concerns/hooks","packages/support/concerns/edgeCases","packages/support/concerns/jsdoc"]},{text:"Exceptions",collapsible:!0,children:["packages/support/exceptions/","packages/support/exceptions/configureCustomError","packages/support/exceptions/configureStackTrace","packages/support/exceptions/getErrorMessage","packages/support/exceptions/customErrors"]},{text:"Meta",collapsible:!0,children:["packages/support/meta/","packages/support/meta/prerequisites","packages/support/meta/supported","packages/support/meta/setAndGet","packages/support/meta/inheritance","packages/support/meta/outsideChanges","packages/support/meta/tc39","packages/support/meta/targetMeta"]},{text:"Mixins",collapsible:!0,children:["packages/support/mixins/","packages/support/mixins/newMixin","packages/support/mixins/apply","packages/support/mixins/instanceof","packages/support/mixins/inheritance","packages/support/mixins/onward"]},{text:"Object",collapsible:!0,children:["packages/support/objects/","packages/support/objects/forget","packages/support/objects/forgetAll","packages/support/objects/get","packages/support/objects/has","packages/support/objects/hasAll","packages/support/objects/hasAny","packages/support/objects/hasUniqueId","packages/support/objects/isCloneable","packages/support/objects/isPopulatable","packages/support/objects/isset","packages/support/objects/merge","packages/support/objects/populate","packages/support/objects/set","packages/support/objects/uniqueId"]},{text:"Reflections",collapsible:!0,children:["packages/support/reflections/","packages/support/reflections/assertHasPrototypeProperty","packages/support/reflections/classLooksLike","packages/support/reflections/classOwnKeys","packages/support/reflections/getAllParentsOfClass","packages/support/reflections/getClassPropertyDescriptor","packages/support/reflections/getClassPropertyDescriptors","packages/support/reflections/getConstructorName","packages/support/reflections/getNameOrDesc","packages/support/reflections/getParentOfClass","packages/support/reflections/hasAllMethods","packages/support/reflections/hasMethod","packages/support/reflections/hasPrototypeProperty","packages/support/reflections/isConstructor","packages/support/reflections/isKeySafe","packages/support/reflections/isKeyUnsafe","packages/support/reflections/isSubclass","packages/support/reflections/isSubclassOrLooksLike","packages/support/reflections/isWeakKind"]},{text:"Misc",collapsible:!0,children:["packages/support/misc/","packages/support/misc/descTag","packages/support/misc/empty","packages/support/misc/isKey","packages/support/misc/isPrimitive","packages/support/misc/isPropertyKey","packages/support/misc/isset","packages/support/misc/mergeKeys","packages/support/misc/toWeakRef"]}]},{text:"Vuepress Utils",collapsible:!0,children:["packages/vuepress-utils/","packages/vuepress-utils/install",{text:"Navigation",collapsible:!0,children:["packages/vuepress-utils/navigation/archive"]},{text:"Plugins",collapsible:!0,children:["packages/vuepress-utils/plugins/last-updated"]},{text:"Components",collapsible:!0,children:["packages/vuepress-utils/components/version-disclaimer"]}]},"packages/xyz/"]}]),C=y.make("v1.x","/v1x",[{text:"Version 1.x",collapsible:!0,children:[""]}]),A=[C,L];var P=class{_name="Archive";_path="/archive";_current;_next;_collections=[];_currentLabel="current";_nextLabel="next";_currentPath="/current";_nextPath="/next";constructor(e,t,r=[]){this._current=e,this._next=t,this.collections=r}static make(e,t,r=[]){return new this(e,t,r)}set name(e){this._name=e}get name(){return this._name}set path(e){this._path=e}get path(){return this._path}set current(e){this._current=e}get current(){return this._current}set next(e){this._next=e}get next(){return this._next}set collections(e){e.forEach((e=>{e.archive=this})),this._collections=e}get collections(){return this.markCurrentAndNextCollections(this._collections)}set currentLabel(e){this._currentLabel=e}get currentLabel(){return this._currentLabel}set nextLabel(e){this._nextLabel=e}get nextLabel(){return this._nextLabel}set currentPath(e){this._currentPath=e}get currentPath(){return this._currentPath}get currentFullPath(){return f(this.path,this.currentPath)}set nextPath(e){this._nextPath=e}get nextPath(){return this._nextPath}get nextFullPath(){return f(this.path,this.nextPath)}asNavigationItem(){return{text:this.name,link:this.path,children:this.makeNavbarItemChildren()}}sidebarConfiguration(){let e={};return this.collections.forEach((t=>{e=Object.assign(e,t.asSidebarObject())})),e}makeNavbarItemChildren(){const e=[];return this.collections.forEach((t=>{e.push(t.asNavigationItem())})),e}markCurrentAndNextCollections(e){return e.forEach((e=>{e===this.next&&(e.name=this.nextLabel,e.path=this.nextPath),e===this.current&&(e.name=this.currentLabel,e.path=this.currentPath)})),e}}.make(L,C,A);const F=(0,h.Lk)("strong",null,"It has not yet been released!",-1),w=(0,h.Lk)("strong",null,"It is no longer supported!",-1);var j={enhance({app:e,router:t,siteData:r}){},setup(){},rootComponents:[],layouts:{Layout:(0,h.pM)({__name:"Layout",setup(e){const t=(0,b.BV)(),r=function(e,t){return(0,h.EW)((()=>x(e,t)))}(t,P),a=function(e,t,r=["/"]){return(0,h.EW)((()=>function(e,t,r=["/"]){const a=e.value.path;return!r.includes(a)&&a!==t.path+"/"&&!x(e,t)&&!function(e,t){const r=t.currentFullPath;return e.value.path.startsWith(r)}(e,t)}(e,t,r)))}(t,P);return(e,t)=>((0,h.uX)(),(0,h.Wv)(g.A,null,{"page-top":(0,h.k6)((()=>[(0,d.R1)(r)?((0,h.uX)(),(0,h.Wv)(v,{key:0},{default:(0,h.k6)((()=>[(0,h.eW)(" You are viewing documentation for an upcoming version. "),F,(0,h.eW)("! ")])),_:1})):(0,h.Q3)("",!0),(0,d.R1)(a)?((0,h.uX)(),(0,h.Wv)(v,{key:1,type:"danger",label:"Warning"},{default:(0,h.k6)((()=>[(0,h.eW)(" You are viewing documentation for an outdated version. "),w])),_:1})):(0,h.Q3)("",!0)])),_:1}))}})}};const I=[a.A,s.A,i.A,l.A,c.A,n.A,o.A,p.A,u.A,j]},3192:function(e,t,r){r.d(t,{J:function(){return s},c:function(){return a}});const a=JSON.parse("{}"),s=Object.fromEntries([["/",{loader:()=>r.e(4470).then(r.bind(r,5924)),meta:{title:""}}],["/archive/",{loader:()=>r.e(4470).then(r.bind(r,4546)),meta:{title:"Archive"}}],["/archive/not_available.html",{loader:()=>r.e(1711).then(r.bind(r,8193)),meta:{title:"Not Available"}}],["/archive/next/",{loader:()=>r.e(4470).then(r.bind(r,9635)),meta:{title:"Not Available"}}],["/archive/current/",{loader:()=>r.e(4470).then(r.bind(r,406)),meta:{title:"Release Notes"}}],["/archive/current/code-of-conduct.html",{loader:()=>r.e(2846).then(r.bind(r,3208)),meta:{title:"Code of Conduct"}}],["/archive/current/contribution-guide.html",{loader:()=>r.e(4975).then(r.bind(r,9476)),meta:{title:"Contribution Guide"}}],["/archive/current/origin.html",{loader:()=>r.e(3066).then(r.bind(r,6885)),meta:{title:"Origin"}}],["/archive/current/security.html",{loader:()=>r.e(9414).then(r.bind(r,4250)),meta:{title:"Security Policy"}}],["/archive/current/upgrade-guide.html",{loader:()=>r.e(5815).then(r.bind(r,8472)),meta:{title:"Upgrade Guide"}}],["/archive/current/packages/",{loader:()=>r.e(4470).then(r.bind(r,7957)),meta:{title:"Introduction"}}],["/archive/current/packages/contracts/",{loader:()=>r.e(4470).then(r.bind(r,8272)),meta:{title:"Introduction"}}],["/archive/current/packages/contracts/install.html",{loader:()=>r.e(469).then(r.bind(r,1640)),meta:{title:"How to install"}}],["/archive/current/packages/support/",{loader:()=>r.e(4470).then(r.bind(r,9046)),meta:{title:"Introduction"}}],["/archive/current/packages/support/install.html",{loader:()=>r.e(469).then(r.bind(r,492)),meta:{title:"How to install"}}],["/archive/current/packages/vuepress-utils/",{loader:()=>r.e(4470).then(r.bind(r,5706)),meta:{title:"Introduction"}}],["/archive/current/packages/vuepress-utils/install.html",{loader:()=>r.e(469).then(r.bind(r,5335)),meta:{title:"How to install"}}],["/archive/current/packages/xyz/",{loader:()=>r.e(4470).then(r.bind(r,1389)),meta:{title:"XYZ (test package)"}}],["/archive/current/packages/support/arrays/",{loader:()=>r.e(4470).then(r.bind(r,2019)),meta:{title:"About Arrays"}}],["/archive/current/packages/support/arrays/includesAll.html",{loader:()=>r.e(4610).then(r.bind(r,5567)),meta:{title:"Includes All"}}],["/archive/current/packages/support/arrays/includesAny.html",{loader:()=>r.e(5943).then(r.bind(r,1465)),meta:{title:"Includes Any"}}],["/archive/current/packages/support/arrays/isArrayLike.html",{loader:()=>r.e(7044).then(r.bind(r,844)),meta:{title:"Is Array Like"}}],["/archive/current/packages/support/arrays/isConcatSpreadable.html",{loader:()=>r.e(7627).then(r.bind(r,4801)),meta:{title:"Is Concat Spreadable"}}],["/archive/current/packages/support/arrays/isSafeArrayLike.html",{loader:()=>r.e(8071).then(r.bind(r,7240)),meta:{title:"Is Safe Array Like"}}],["/archive/current/packages/support/arrays/isTypedArray.html",{loader:()=>r.e(6805).then(r.bind(r,7187)),meta:{title:"Is Typed Array"}}],["/archive/current/packages/support/arrays/merge.html",{loader:()=>r.e(8802).then(r.bind(r,6060)),meta:{title:"Merge"}}],["/archive/current/packages/support/concerns/",{loader:()=>r.e(4470).then(r.bind(r,990)),meta:{title:"About Concerns"}}],["/archive/current/packages/support/concerns/aliases.html",{loader:()=>r.e(9994).then(r.bind(r,3981)),meta:{title:"Aliases"}}],["/archive/current/packages/support/concerns/booting.html",{loader:()=>r.e(86).then(r.bind(r,5008)),meta:{title:"Booting"}}],["/archive/current/packages/support/concerns/concernClass.html",{loader:()=>r.e(9628).then(r.bind(r,6482)),meta:{title:"Concern Class"}}],["/archive/current/packages/support/concerns/conflictResolution.html",{loader:()=>r.e(7428).then(r.bind(r,7321)),meta:{title:"Conflict Resolution"}}],["/archive/current/packages/support/concerns/edgeCases.html",{loader:()=>r.e(7920).then(r.bind(r,54)),meta:{title:"Edge Cases"}}],["/archive/current/packages/support/concerns/hooks.html",{loader:()=>r.e(2822).then(r.bind(r,6940)),meta:{title:"Hooks"}}],["/archive/current/packages/support/concerns/jsdoc.html",{loader:()=>r.e(6745).then(r.bind(r,7975)),meta:{title:"JSDoc"}}],["/archive/current/packages/support/concerns/prerequisites.html",{loader:()=>r.e(4261).then(r.bind(r,840)),meta:{title:"Prerequisites"}}],["/archive/current/packages/support/concerns/usage.html",{loader:()=>r.e(6333).then(r.bind(r,3623)),meta:{title:"Using Concerns"}}],["/archive/current/packages/support/exceptions/",{loader:()=>r.e(4470).then(r.bind(r,4535)),meta:{title:"About Exceptions"}}],["/archive/current/packages/support/exceptions/configureCustomError.html",{loader:()=>r.e(5807).then(r.bind(r,3866)),meta:{title:"Configure Custom Error"}}],["/archive/current/packages/support/exceptions/configureStackTrace.html",{loader:()=>r.e(3783).then(r.bind(r,2349)),meta:{title:"Configure Stack Trace"}}],["/archive/current/packages/support/exceptions/customErrors.html",{loader:()=>r.e(1816).then(r.bind(r,8394)),meta:{title:"Custom Errors"}}],["/archive/current/packages/support/exceptions/getErrorMessage.html",{loader:()=>r.e(9127).then(r.bind(r,5312)),meta:{title:"Get Error Message"}}],["/archive/current/packages/support/meta/",{loader:()=>r.e(4470).then(r.bind(r,332)),meta:{title:"About Meta"}}],["/archive/current/packages/support/meta/inheritance.html",{loader:()=>r.e(6406).then(r.bind(r,6501)),meta:{title:"Inheritance"}}],["/archive/current/packages/support/meta/outsideChanges.html",{loader:()=>r.e(5180).then(r.bind(r,1276)),meta:{title:"Outside Changes"}}],["/archive/current/packages/support/meta/prerequisites.html",{loader:()=>r.e(4261).then(r.bind(r,5265)),meta:{title:"Prerequisites"}}],["/archive/current/packages/support/meta/setAndGet.html",{loader:()=>r.e(3717).then(r.bind(r,3321)),meta:{title:"Set & Get"}}],["/archive/current/packages/support/meta/supported.html",{loader:()=>r.e(3248).then(r.bind(r,2302)),meta:{title:"Supported Elements"}}],["/archive/current/packages/support/meta/targetMeta.html",{loader:()=>r.e(676).then(r.bind(r,3037)),meta:{title:"Target Meta"}}],["/archive/current/packages/support/meta/tc39.html",{loader:()=>r.e(2021).then(r.bind(r,8338)),meta:{title:"TC39 Proposal"}}],["/archive/current/packages/support/misc/",{loader:()=>r.e(4470).then(r.bind(r,6892)),meta:{title:"About Misc."}}],["/archive/current/packages/support/misc/descTag.html",{loader:()=>r.e(9061).then(r.bind(r,9018)),meta:{title:"Desc. Tag"}}],["/archive/current/packages/support/misc/empty.html",{loader:()=>r.e(3295).then(r.bind(r,6616)),meta:{title:"Empty"}}],["/archive/current/packages/support/misc/isKey.html",{loader:()=>r.e(6529).then(r.bind(r,9845)),meta:{title:"Is Key"}}],["/archive/current/packages/support/misc/isPrimitive.html",{loader:()=>r.e(9383).then(r.bind(r,5615)),meta:{title:"Is Primitive"}}],["/archive/current/packages/support/misc/isPropertyKey.html",{loader:()=>r.e(5394).then(r.bind(r,9937)),meta:{title:"Is Property Key"}}],["/archive/current/packages/support/misc/isset.html",{loader:()=>r.e(9502).then(r.bind(r,8632)),meta:{title:"Isset"}}],["/archive/current/packages/support/misc/mergeKeys.html",{loader:()=>r.e(338).then(r.bind(r,903)),meta:{title:"Merge Keys"}}],["/archive/current/packages/support/misc/toWeakRef.html",{loader:()=>r.e(9912).then(r.bind(r,9904)),meta:{title:"To Weak Ref."}}],["/archive/current/packages/support/mixins/",{loader:()=>r.e(4470).then(r.bind(r,1205)),meta:{title:"About Mixins"}}],["/archive/current/packages/support/mixins/apply.html",{loader:()=>r.e(6262).then(r.bind(r,5126)),meta:{title:"Apply Mixins"}}],["/archive/current/packages/support/mixins/inheritance.html",{loader:()=>r.e(6406).then(r.bind(r,4661)),meta:{title:"Inheritance"}}],["/archive/current/packages/support/mixins/instanceof.html",{loader:()=>r.e(5646).then(r.bind(r,6192)),meta:{title:"Instanceof"}}],["/archive/current/packages/support/mixins/newMixin.html",{loader:()=>r.e(9495).then(r.bind(r,6251)),meta:{title:"New Mixin"}}],["/archive/current/packages/support/mixins/onward.html",{loader:()=>r.e(8411).then(r.bind(r,2231)),meta:{title:"Onward"}}],["/archive/current/packages/support/objects/",{loader:()=>r.e(4470).then(r.bind(r,3122)),meta:{title:"About Objects"}}],["/archive/current/packages/support/objects/forget.html",{loader:()=>r.e(4993).then(r.bind(r,8407)),meta:{title:"Forget"}}],["/archive/current/packages/support/objects/forgetAll.html",{loader:()=>r.e(7922).then(r.bind(r,8668)),meta:{title:"Forget All"}}],["/archive/current/packages/support/objects/get.html",{loader:()=>r.e(754).then(r.bind(r,9657)),meta:{title:"Get"}}],["/archive/current/packages/support/objects/has.html",{loader:()=>r.e(3246).then(r.bind(r,9586)),meta:{title:"Has"}}],["/archive/current/packages/support/objects/hasAll.html",{loader:()=>r.e(8423).then(r.bind(r,1747)),meta:{title:"Has All"}}],["/archive/current/packages/support/objects/hasAny.html",{loader:()=>r.e(9150).then(r.bind(r,4079)),meta:{title:"Has Any"}}],["/archive/current/packages/support/objects/hasUniqueId.html",{loader:()=>r.e(8852).then(r.bind(r,1862)),meta:{title:"Has Unique ID"}}],["/archive/current/packages/support/objects/isCloneable.html",{loader:()=>r.e(6247).then(r.bind(r,2572)),meta:{title:"Is Cloneable"}}],["/archive/current/packages/support/objects/isPopulatable.html",{loader:()=>r.e(6321).then(r.bind(r,6565)),meta:{title:"Is Populatable"}}],["/archive/current/packages/support/objects/isset.html",{loader:()=>r.e(9502).then(r.bind(r,906)),meta:{title:"Isset"}}],["/archive/current/packages/support/objects/merge.html",{loader:()=>r.e(8802).then(r.bind(r,820)),meta:{title:"Merge"}}],["/archive/current/packages/support/objects/populate.html",{loader:()=>r.e(7902).then(r.bind(r,5766)),meta:{title:"Populate"}}],["/archive/current/packages/support/objects/set.html",{loader:()=>r.e(3518).then(r.bind(r,9819)),meta:{title:"Set"}}],["/archive/current/packages/support/objects/uniqueId.html",{loader:()=>r.e(6054).then(r.bind(r,7717)),meta:{title:"Unique ID"}}],["/archive/current/packages/support/reflections/",{loader:()=>r.e(4470).then(r.bind(r,1783)),meta:{title:"About reflections"}}],["/archive/current/packages/support/reflections/assertHasPrototypeProperty.html",{loader:()=>r.e(8925).then(r.bind(r,3644)),meta:{title:"Assert Has Prototype Prop."}}],["/archive/current/packages/support/reflections/classLooksLike.html",{loader:()=>r.e(2023).then(r.bind(r,1274)),meta:{title:"Class Looks Like"}}],["/archive/current/packages/support/reflections/classOwnKeys.html",{loader:()=>r.e(984).then(r.bind(r,5491)),meta:{title:"Class Own Keys"}}],["/archive/current/packages/support/reflections/getAllParentsOfClass.html",{loader:()=>r.e(3503).then(r.bind(r,7210)),meta:{title:"Get All Parents Of Class"}}],["/archive/current/packages/support/reflections/getClassPropertyDescriptor.html",{loader:()=>r.e(572).then(r.bind(r,463)),meta:{title:"Get Class Prop. Descriptor"}}],["/archive/current/packages/support/reflections/getClassPropertyDescriptors.html",{loader:()=>r.e(9443).then(r.bind(r,9595)),meta:{title:"Get Class Prop. Descriptors"}}],["/archive/current/packages/support/reflections/getConstructorName.html",{loader:()=>r.e(9583).then(r.bind(r,3252)),meta:{title:"Get Constructor Name"}}],["/archive/current/packages/support/reflections/getNameOrDesc.html",{loader:()=>r.e(1705).then(r.bind(r,2075)),meta:{title:"Get Name Or Desc. Tag"}}],["/archive/current/packages/support/reflections/getParentOfClass.html",{loader:()=>r.e(5067).then(r.bind(r,7836)),meta:{title:"Get Parent Of Class"}}],["/archive/current/packages/support/reflections/hasAllMethods.html",{loader:()=>r.e(1901).then(r.bind(r,3184)),meta:{title:"Has All Methods"}}],["/archive/current/packages/support/reflections/hasMethod.html",{loader:()=>r.e(8483).then(r.bind(r,8869)),meta:{title:"Has Method"}}],["/archive/current/packages/support/reflections/hasPrototypeProperty.html",{loader:()=>r.e(3287).then(r.bind(r,6195)),meta:{title:"Has Prototype Property"}}],["/archive/current/packages/support/reflections/isConstructor.html",{loader:()=>r.e(8874).then(r.bind(r,5843)),meta:{title:"Is Constructor"}}],["/archive/current/packages/support/reflections/isKeySafe.html",{loader:()=>r.e(9088).then(r.bind(r,6536)),meta:{title:"Is Key Safe"}}],["/archive/current/packages/support/reflections/isKeyUnsafe.html",{loader:()=>r.e(4795).then(r.bind(r,715)),meta:{title:"Is Key Unsafe"}}],["/archive/current/packages/support/reflections/isSubclass.html",{loader:()=>r.e(3082).then(r.bind(r,2408)),meta:{title:"Is Subclass"}}],["/archive/current/packages/support/reflections/isSubclassOrLooksLike.html",{loader:()=>r.e(4648).then(r.bind(r,5092)),meta:{title:"Is Subclass Or Looks Like"}}],["/archive/current/packages/support/reflections/isWeakKind.html",{loader:()=>r.e(9058).then(r.bind(r,6820)),meta:{title:"Is WeakKind"}}],["/archive/current/packages/vuepress-utils/components/version-disclaimer.html",{loader:()=>r.e(6368).then(r.bind(r,6679)),meta:{title:"Version Disclaimer"}}],["/archive/current/packages/vuepress-utils/navigation/archive.html",{loader:()=>r.e(3100).then(r.bind(r,4655)),meta:{title:"Archive"}}],["/archive/current/packages/vuepress-utils/plugins/last-updated.html",{loader:()=>r.e(8518).then(r.bind(r,4306)),meta:{title:"Last Updated"}}],["/404.html",{loader:()=>r.e(7490).then(r.bind(r,2632)),meta:{title:""}}]])},8752:function(e,t,r){r.d(t,{b:function(){return a}});const a=[{title:"Release Notes",headers:[{level:2,title:"Support Policy",slug:"support-policy",link:"#support-policy",children:[]},{level:2,title:"v0.x Highlights",slug:"v0-x-highlights",link:"#v0-x-highlights",children:[{level:3,title:"Concerns",slug:"concerns",link:"#concerns",children:[]},{level:3,title:"Merge",slug:"merge",link:"#merge",children:[]},{level:3,title:"Mixins",slug:"mixins",link:"#mixins",children:[]},{level:3,title:'"Target" Meta Decorator',slug:"target-meta-decorator",link:"#target-meta-decorator",children:[]},{level:3,title:"Meta Decorator",slug:"meta-decorator",link:"#meta-decorator",children:[]},{level:3,title:"Support",slug:"support",link:"#support",children:[]},{level:3,title:"Vuepress Utils",slug:"vuepress-utils",link:"#vuepress-utils",children:[]}]}],path:"/archive/current/",pathLocale:"/",extraFields:["Ion Release Notes"]},{title:"Code of Conduct",headers:[{level:2,title:"Consequences",slug:"consequences",link:"#consequences",children:[]}],path:"/archive/current/code-of-conduct.html",pathLocale:"/",extraFields:["Code of Conduct - Ion"]},{title:"Contribution Guide",headers:[{level:2,title:"Bug Report",slug:"bug-report",link:"#bug-report",children:[]},{level:2,title:"Security Vulnerability",slug:"security-vulnerability",link:"#security-vulnerability",children:[]},{level:2,title:"Feature Request",slug:"feature-request",link:"#feature-request",children:[]},{level:2,title:"Fork, code and send pull-request",slug:"fork-code-and-send-pull-request",link:"#fork-code-and-send-pull-request",children:[]}],path:"/archive/current/contribution-guide.html",pathLocale:"/",extraFields:["How to contribute to Ion"]},{title:"Origin",headers:[{level:2,title:'Meaning of "Ion"',slug:"meaning-of-ion",link:"#meaning-of-ion",children:[]}],path:"/archive/current/origin.html",pathLocale:"/",extraFields:["The origin of Ion"]},{title:"Security Policy",headers:[{level:2,title:"How to report a vulnerability",slug:"how-to-report-a-vulnerability",link:"#how-to-report-a-vulnerability",children:[{level:3,title:"Public PGP Key",slug:"public-pgp-key",link:"#public-pgp-key",children:[]}]},{level:2,title:"Supported Versions",slug:"supported-versions",link:"#supported-versions",children:[]}],path:"/archive/current/security.html",pathLocale:"/",extraFields:["Security Policy of Athenaeum"]},{title:"Upgrade Guide",headers:[{level:2,title:"From v0.7.x- to v0.10.x",slug:"from-v0-7-x-to-v0-10-x",link:"#from-v0-7-x-to-v0-10-x",children:[{level:3,title:"Meta (types)",slug:"meta-types",link:"#meta-types",children:[]},{level:3,title:"Meta (targetMeta() and inheritTargetMeta())",slug:"meta-targetmeta-and-inherittargetmeta",link:"#meta-targetmeta-and-inherittargetmeta",children:[]}]},{level:2,title:"From v0.6.x to v0.7.x",slug:"from-v0-6-x-to-v0-7-x",link:"#from-v0-6-x-to-v0-7-x",children:[{level:3,title:"Node.js 20.11.0 Required",slug:"node-js-20-11-0-required",link:"#node-js-20-11-0-required",children:[]},{level:3,title:"Meta",slug:"meta",link:"#meta",children:[]},{level:3,title:"Vuepress Utils",slug:"vuepress-utils",link:"#vuepress-utils",children:[]}]},{level:2,title:"From v0.3.x to v0.4.x",slug:"from-v0-3-x-to-v0-4-x",link:"#from-v0-3-x-to-v0-4-x",children:[{level:3,title:"Rest Parameters for forgetAll(), hasAll() and hasAny()",slug:"rest-parameters-for-forgetall-hasall-and-hasany",link:"#rest-parameters-for-forgetall-hasall-and-hasany",children:[]}]},{level:2,title:"Onward",slug:"onward",link:"#onward",children:[]}],path:"/archive/current/upgrade-guide.html",pathLocale:"/",extraFields:["Ion Upgrade Guide"]},{title:"Introduction",headers:[{level:2,title:"Environment",slug:"environment",link:"#environment",children:[]},{level:2,title:"Available Since",slug:"available-since",link:"#available-since",children:[]},{level:2,title:"Not Released",slug:"not-released",link:"#not-released",children:[]},{level:2,title:"Not Published",slug:"not-published",link:"#not-published",children:[]}],path:"/archive/current/packages/",pathLocale:"/",extraFields:["Ion Packages"]},{title:"Introduction",headers:[],path:"/archive/current/packages/contracts/",pathLocale:"/",extraFields:["Ion Support package"]},{title:"How to install",headers:[{level:2,title:"npm",slug:"npm",link:"#npm",children:[]},{level:2,title:"yarn",slug:"yarn",link:"#yarn",children:[]},{level:2,title:"pnpm",slug:"pnpm",link:"#pnpm",children:[]}],path:"/archive/current/packages/contracts/install.html",pathLocale:"/",extraFields:["How to install Ion Contracts package"]},{title:"Introduction",headers:[],path:"/archive/current/packages/support/",pathLocale:"/",extraFields:["Ion Support package"]},{title:"How to install",headers:[{level:2,title:"npm",slug:"npm",link:"#npm",children:[]},{level:2,title:"yarn",slug:"yarn",link:"#yarn",children:[]},{level:2,title:"pnpm",slug:"pnpm",link:"#pnpm",children:[]}],path:"/archive/current/packages/support/install.html",pathLocale:"/",extraFields:["How to install Ion Support package"]},{title:"Introduction",headers:[{level:2,title:"Navigation",slug:"navigation",link:"#navigation",children:[]},{level:2,title:"Plugins",slug:"plugins",link:"#plugins",children:[]},{level:2,title:"Components",slug:"components",link:"#components",children:[]}],path:"/archive/current/packages/vuepress-utils/",pathLocale:"/",extraFields:["Various helpers for your vuepress site"]},{title:"How to install",headers:[{level:2,title:"npm",slug:"npm",link:"#npm",children:[]},{level:2,title:"yarn",slug:"yarn",link:"#yarn",children:[]},{level:2,title:"pnpm",slug:"pnpm",link:"#pnpm",children:[]}],path:"/archive/current/packages/vuepress-utils/install.html",pathLocale:"/",extraFields:["How to install Vuepress Utils"]},{title:"XYZ (test package)",headers:[],path:"/archive/current/packages/xyz/",pathLocale:"/",extraFields:["XYZ Test Package"]},{title:"About Arrays",headers:[],path:"/archive/current/packages/support/arrays/",pathLocale:"/",extraFields:["Array utilities."]},{title:"Includes All",headers:[],path:"/archive/current/packages/support/arrays/includesAll.html",pathLocale:"/",extraFields:["Determine if array contains all values."]},{title:"Includes Any",headers:[],path:"/archive/current/packages/support/arrays/includesAny.html",pathLocale:"/",extraFields:["Determine if array contains some values."]},{title:"Is Array Like",headers:[],path:"/archive/current/packages/support/arrays/isArrayLike.html",pathLocale:"/",extraFields:["Determine if value is array-like."]},{title:"Is Concat Spreadable",headers:[],path:"/archive/current/packages/support/arrays/isConcatSpreadable.html",pathLocale:"/",extraFields:["Determine if object is concat spreadable."]},{title:"Is Safe Array Like",headers:[],path:"/archive/current/packages/support/arrays/isSafeArrayLike.html",pathLocale:"/",extraFields:['Determine if value is "safe" array-like.']},{title:"Is Typed Array",headers:[],path:"/archive/current/packages/support/arrays/isTypedArray.html",pathLocale:"/",extraFields:["Determine if object is a typed array."]},{title:"Merge",headers:[{level:2,title:"Deep Copy Objects",slug:"deep-copy-objects",link:"#deep-copy-objects",children:[]},{level:2,title:"When unable to merge values",slug:"when-unable-to-merge-values",link:"#when-unable-to-merge-values",children:[]}],path:"/archive/current/packages/support/arrays/merge.html",pathLocale:"/",extraFields:["Merge multiple arrays into a new array."]},{title:"About Concerns",headers:[{level:2,title:"Example",slug:"example",link:"#example",children:[]}],path:"/archive/current/packages/support/concerns/",pathLocale:"/",extraFields:["Alternative mixin utility."]},{title:"Aliases",headers:[{level:2,title:"Properties & Methods",slug:"properties-methods",link:"#properties-methods",children:[]},{level:2,title:"If property or method already exists",slug:"if-property-or-method-already-exists",link:"#if-property-or-method-already-exists",children:[]}],path:"/archive/current/packages/support/concerns/aliases.html",pathLocale:"/",extraFields:["What are aliases"]},{title:"Booting",headers:[{level:2,title:"Manual Booting",slug:"manual-booting",link:"#manual-booting",children:[{level:3,title:"Boot All Concerns",slug:"boot-all-concerns",link:"#boot-all-concerns",children:[]}]}],path:"/archive/current/packages/support/concerns/booting.html",pathLocale:"/",extraFields:["How concerns are booted"]},{title:"Concern Class",headers:[{level:2,title:"Inherit from AbstractConcern",slug:"inherit-from-abstractconcern",link:"#inherit-from-abstractconcern",children:[{level:3,title:"Private Members",slug:"private-members",link:"#private-members",children:[]},{level:3,title:"Static Members",slug:"static-members",link:"#static-members",children:[]},{level:3,title:"Transpilers",slug:"transpilers",link:"#transpilers",children:[]}]},{level:2,title:'Customise "alias" members',slug:"customise-alias-members",link:"#customise-alias-members",children:[]},{level:2,title:"Concern Owner instance",slug:"concern-owner-instance",link:"#concern-owner-instance",children:[]},{level:2,title:"Constructor",slug:"constructor",link:"#constructor",children:[]}],path:"/archive/current/packages/support/concerns/concernClass.html",pathLocale:"/",extraFields:["How to define a new concern class."]},{title:"Conflict Resolution",headers:[{level:2,title:"Naming Conflicts",slug:"naming-conflicts",link:"#naming-conflicts",children:[]},{level:2,title:"Resolve Naming Conflicts",slug:"resolve-naming-conflicts",link:"#resolve-naming-conflicts",children:[]},{level:2,title:"Prevent Aliases",slug:"prevent-aliases",link:"#prevent-aliases",children:[]},{level:2,title:"Shorthand Configuration",slug:"shorthand-configuration",link:"#shorthand-configuration",children:[]}],path:"/archive/current/packages/support/concerns/conflictResolution.html",pathLocale:"/",extraFields:["How to deal with naming conflicts"]},{title:"Edge Cases",headers:[{level:2,title:"Getter & Setter declared in different concerns",slug:"getter-setter-declared-in-different-concerns",link:"#getter-setter-declared-in-different-concerns",children:[]},{level:2,title:"Inheritance vs. Concern members",slug:"inheritance-vs-concern-members",link:"#inheritance-vs-concern-members",children:[]},{level:2,title:"Concerns using other concerns",slug:"concerns-using-other-concerns",link:"#concerns-using-other-concerns",children:[]}],path:"/archive/current/packages/support/concerns/edgeCases.html",pathLocale:"/",extraFields:["A few edge cases when making or using concerns"]},{title:"Hooks",headers:[{level:2,title:"BEFORE Registration",slug:"before-registration",link:"#before-registration",children:[]},{level:2,title:"AFTER Registration",slug:"after-registration",link:"#after-registration",children:[]}],path:"/archive/current/packages/support/concerns/hooks.html",pathLocale:"/",extraFields:["How to use registration hooks"]},{title:"JSDoc",headers:[{level:2,title:"@mixin and @mixes",slug:"mixin-and-mixes",link:"#mixin-and-mixes",children:[]},{level:2,title:"@property",slug:"property",link:"#property",children:[]},{level:2,title:"@borrows",slug:"borrows",link:"#borrows",children:[]},{level:2,title:"@member",slug:"member",link:"#member",children:[]}],path:"/archive/current/packages/support/concerns/jsdoc.html",pathLocale:"/",extraFields:["A wat to document what concerns a targer class uses."]},{title:"Prerequisites",headers:[],path:"/archive/current/packages/support/concerns/prerequisites.html",pathLocale:"/",extraFields:["Prerequisites for using concerns."]},{title:"Using Concerns",headers:[{level:2,title:"Using Concerns",slug:"using-concerns",link:"#using-concerns",children:[]},{level:2,title:"Inheritance",slug:"inheritance",link:"#inheritance",children:[]},{level:2,title:"Manual interaction",slug:"manual-interaction",link:"#manual-interaction",children:[{level:3,title:"Determine if target uses concerns",slug:"determine-if-target-uses-concerns",link:"#determine-if-target-uses-concerns",children:[]}]}],path:"/archive/current/packages/support/concerns/usage.html",pathLocale:"/",extraFields:["How to use concerns."]},{title:"About Exceptions",headers:[],path:"/archive/current/packages/support/exceptions/",pathLocale:"/",extraFields:["Error & Exceptions utilities."]},{title:"Configure Custom Error",headers:[{level:2,title:"Arguments",slug:"arguments",link:"#arguments",children:[]}],path:"/archive/current/packages/support/exceptions/configureCustomError.html",pathLocale:"/",extraFields:["Configuration of custom errors."]},{title:"Configure Stack Trace",headers:[{level:2,title:"Arguments",slug:"arguments",link:"#arguments",children:[]}],path:"/archive/current/packages/support/exceptions/configureStackTrace.html",pathLocale:"/",extraFields:["Configuration error stack trace."]},{title:"Custom Errors",headers:[{level:2,title:"AbstractClassError",slug:"abstractclasserror",link:"#abstractclasserror",children:[]},{level:2,title:"LogicalError",slug:"logicalerror",link:"#logicalerror",children:[]}],path:"/archive/current/packages/support/exceptions/customErrors.html",pathLocale:"/",extraFields:["Predefined custom errors."]},{title:"Get Error Message",headers:[],path:"/archive/current/packages/support/exceptions/getErrorMessage.html",pathLocale:"/",extraFields:["Obtain error or default message."]},{title:"About Meta",headers:[],path:"/archive/current/packages/support/meta/",pathLocale:"/",extraFields:["Add arbitrary metadata on classes, methods and properties."]},{title:"Inheritance",headers:[{level:2,title:"Overwrites",slug:"overwrites",link:"#overwrites",children:[]}],path:"/archive/current/packages/support/meta/inheritance.html",pathLocale:"/",extraFields:["About metadata inheritance and overwrites."]},{title:"Outside Changes",headers:[],path:"/archive/current/packages/support/meta/outsideChanges.html",pathLocale:"/",extraFields:["About changes to metadata outside decorator scope."]},{title:"Prerequisites",headers:[],path:"/archive/current/packages/support/meta/prerequisites.html",pathLocale:"/",extraFields:["Prerequisites for using meta decorators."]},{title:"Set & Get",headers:[{level:2,title:"Set Metadata",slug:"set-metadata",link:"#set-metadata",children:[]},{level:2,title:"Get Metadata",slug:"get-metadata",link:"#get-metadata",children:[{level:3,title:"Default Value",slug:"default-value",link:"#default-value",children:[]}]},{level:2,title:"Callback",slug:"callback",link:"#callback",children:[]}],path:"/archive/current/packages/support/meta/setAndGet.html",pathLocale:"/",extraFields:["Defining and retrieving metadata."]},{title:"Supported Elements",headers:[],path:"/archive/current/packages/support/meta/supported.html",pathLocale:"/",extraFields:["Supported elements by meta decorators."]},{title:"Target Meta",headers:[{level:2,title:"Supported Elements",slug:"supported-elements",link:"#supported-elements",children:[]},{level:2,title:"Class Instance",slug:"class-instance",link:"#class-instance",children:[]},{level:2,title:"Method Reference",slug:"method-reference",link:"#method-reference",children:[]},{level:2,title:"Inheritance",slug:"inheritance",link:"#inheritance",children:[{level:3,title:"Static Methods",slug:"static-methods",link:"#static-methods",children:[]}]}],path:"/archive/current/packages/support/meta/targetMeta.html",pathLocale:"/",extraFields:["Associate metadata with a class instance or class method reference."]},{title:"TC39 Proposal",headers:[],path:"/archive/current/packages/support/meta/tc39.html",pathLocale:"/",extraFields:["In relation to TC39 Decorator Metadata proposal"]},{title:"About Misc.",headers:[],path:"/archive/current/packages/support/misc/",pathLocale:"/",extraFields:["Miscellaneous utility functions."]},{title:"Desc. Tag",headers:[],path:"/archive/current/packages/support/misc/descTag.html",pathLocale:"/",extraFields:["Object description tag"]},{title:"Empty",headers:[{level:2,title:"WeakMap and WeakSet",slug:"weakmap-and-weakset",link:"#weakmap-and-weakset",children:[]}],path:"/archive/current/packages/support/misc/empty.html",pathLocale:"/",extraFields:["Determine if value is empty"]},{title:"Is Key",headers:[],path:"/archive/current/packages/support/misc/isKey.html",pathLocale:"/",extraFields:["Determine if is a key or path identifier"]},{title:"Is Primitive",headers:[],path:"/archive/current/packages/support/misc/isPrimitive.html",pathLocale:"/",extraFields:["Determine if value is a primitive."]},{title:"Is Property Key",headers:[],path:"/archive/current/packages/support/misc/isPropertyKey.html",pathLocale:"/",extraFields:["Determine if key is a valid property key name."]},{title:"Isset",headers:[{level:2,title:"Multiple values",slug:"multiple-values",link:"#multiple-values",children:[]}],path:"/archive/current/packages/support/misc/isset.html",pathLocale:"/",extraFields:["Determine if value isset."]},{title:"Merge Keys",headers:[],path:"/archive/current/packages/support/misc/mergeKeys.html",pathLocale:"/",extraFields:["Merge keys into a single key."]},{title:"To Weak Ref.",headers:[],path:"/archive/current/packages/support/misc/toWeakRef.html",pathLocale:"/",extraFields:["Wrap object into a Weak Reference."]},{title:"About Mixins",headers:[],path:"/archive/current/packages/support/mixins/",pathLocale:"/",extraFields:['Abstract subclasses ("Mixins") utilities']},{title:"Apply Mixins",headers:[{level:2,title:"Extending Superclass",slug:"extending-superclass",link:"#extending-superclass",children:[]}],path:"/archive/current/packages/support/mixins/apply.html",pathLocale:"/",extraFields:["How to apply mixins."]},{title:"Inheritance",headers:[],path:"/archive/current/packages/support/mixins/inheritance.html",pathLocale:"/",extraFields:["How inheritance works."]},{title:"Instanceof",headers:[],path:"/archive/current/packages/support/mixins/instanceof.html",pathLocale:"/",extraFields:["Using instanceof operator."]},{title:"New Mixin",headers:[{level:2,title:"Constructor",slug:"constructor",link:"#constructor",children:[]}],path:"/archive/current/packages/support/mixins/newMixin.html",pathLocale:"/",extraFields:["How to defined a new Mixin class."]},{title:"Onward",headers:[],path:"/archive/current/packages/support/mixins/onward.html",pathLocale:"/",extraFields:["Where to read more about mixins."]},{title:"About Objects",headers:[],path:"/archive/current/packages/support/objects/",pathLocale:"/",extraFields:["Objects related utilities"]},{title:"Forget",headers:[],path:"/archive/current/packages/support/objects/forget.html",pathLocale:"/",extraFields:["Forget object key"]},{title:"Forget All",headers:[],path:"/archive/current/packages/support/objects/forgetAll.html",pathLocale:"/",extraFields:["Forget object all keys"]},{title:"Get",headers:[{level:2,title:"Default Value",slug:"default-value",link:"#default-value",children:[]}],path:"/archive/current/packages/support/objects/get.html",pathLocale:"/",extraFields:["Get object value from given path."]},{title:"Has",headers:[],path:"/archive/current/packages/support/objects/has.html",pathLocale:"/",extraFields:["Determine if object path is a property."]},{title:"Has All",headers:[],path:"/archive/current/packages/support/objects/hasAll.html",pathLocale:"/",extraFields:["Determine if all object paths are properties."]},{title:"Has Any",headers:[],path:"/archive/current/packages/support/objects/hasAny.html",pathLocale:"/",extraFields:["Determine if any object paths are properties."]},{title:"Has Unique ID",headers:[],path:"/archive/current/packages/support/objects/hasUniqueId.html",pathLocale:"/",extraFields:["Determine if object has a unique id."]},{title:"Is Cloneable",headers:[],path:"/archive/current/packages/support/objects/isCloneable.html",pathLocale:"/",extraFields:["Determine if object is cloneable."]},{title:"Is Populatable",headers:[],path:"/archive/current/packages/support/objects/isPopulatable.html",pathLocale:"/",extraFields:["Determine if object is populatable."]},{title:"Isset",headers:[],path:"/archive/current/packages/support/objects/isset.html",pathLocale:"/",extraFields:["Determine if object object paths are set and have values."]},{title:"Merge",headers:[{level:2,title:"Shallow Copied Types",slug:"shallow-copied-types",link:"#shallow-copied-types",children:[]},{level:2,title:"Unsafe Keys",slug:"unsafe-keys",link:"#unsafe-keys",children:[]},{level:2,title:"Merge Options",slug:"merge-options",link:"#merge-options",children:[{level:3,title:"depth",slug:"depth",link:"#depth",children:[]},{level:3,title:"skip",slug:"skip",link:"#skip",children:[]},{level:3,title:"overwriteWithUndefined",slug:"overwritewithundefined",link:"#overwritewithundefined",children:[]},{level:3,title:"useCloneable",slug:"usecloneable",link:"#usecloneable",children:[]},{level:3,title:"mergeArrays",slug:"mergearrays",link:"#mergearrays",children:[]},{level:3,title:"callback",slug:"callback",link:"#callback",children:[]}]}],path:"/archive/current/packages/support/objects/merge.html",pathLocale:"/",extraFields:["Merge multiple objects into a new object."]},{title:"Populate",headers:[{level:2,title:"Arguments",slug:"arguments",link:"#arguments",children:[]},{level:2,title:"Limit keys to populate",slug:"limit-keys-to-populate",link:"#limit-keys-to-populate",children:[]},{level:2,title:"Source Keys Callback",slug:"source-keys-callback",link:"#source-keys-callback",children:[]},{level:2,title:"When keys do not exist",slug:"when-keys-do-not-exist",link:"#when-keys-do-not-exist",children:[]}],path:"/archive/current/packages/support/objects/populate.html",pathLocale:"/",extraFields:["Populate target object."]},{title:"Set",headers:[],path:"/archive/current/packages/support/objects/set.html",pathLocale:"/",extraFields:["Set value in object path."]},{title:"Unique ID",headers:[],path:"/archive/current/packages/support/objects/uniqueId.html",pathLocale:"/",extraFields:["Set value in object path."]},{title:"About reflections",headers:[],path:"/archive/current/packages/support/reflections/",pathLocale:"/",extraFields:["Reflection utilities."]},{title:"Assert Has Prototype Prop.",headers:[],path:"/archive/current/packages/support/reflections/assertHasPrototypeProperty.html",pathLocale:"/",extraFields:['Assert that object has a "prototype" property.']},{title:"Class Looks Like",headers:[{level:2,title:"Arguments",slug:"arguments",link:"#arguments",children:[]},{level:2,title:"Class Blueprint",slug:"class-blueprint",link:"#class-blueprint",children:[]},{level:2,title:"Recursive",slug:"recursive",link:"#recursive",children:[]}],path:"/archive/current/packages/support/reflections/classLooksLike.html",pathLocale:"/",extraFields:["Determine if a class looks like blueprint."]},{title:"Class Own Keys",headers:[{level:2,title:"Limitation",slug:"limitation",link:"#limitation",children:[]}],path:"/archive/current/packages/support/reflections/classOwnKeys.html",pathLocale:"/",extraFields:["Get property keys of target class."]},{title:"Get All Parents Of Class",headers:[],path:"/archive/current/packages/support/reflections/getAllParentsOfClass.html",pathLocale:"/",extraFields:["Get all parents of a class"]},{title:"Get Class Prop. Descriptor",headers:[],path:"/archive/current/packages/support/reflections/getClassPropertyDescriptor.html",pathLocale:"/",extraFields:["Get property descriptor of target class property."]},{title:"Get Class Prop. Descriptors",headers:[],path:"/archive/current/packages/support/reflections/getClassPropertyDescriptors.html",pathLocale:"/",extraFields:["Get all property descriptors of target class."]},{title:"Get Constructor Name",headers:[],path:"/archive/current/packages/support/reflections/getConstructorName.html",pathLocale:"/",extraFields:["Get name of target class' constructor"]},{title:"Get Name Or Desc. Tag",headers:[],path:"/archive/current/packages/support/reflections/getNameOrDesc.html",pathLocale:"/",extraFields:["Get name of target class' constructor, or description tag"]},{title:"Get Parent Of Class",headers:[],path:"/archive/current/packages/support/reflections/getParentOfClass.html",pathLocale:"/",extraFields:["Get parent class."]},{title:"Has All Methods",headers:[],path:"/archive/current/packages/support/reflections/hasAllMethods.html",pathLocale:"/",extraFields:["Determine if target has all methods"]},{title:"Has Method",headers:[],path:"/archive/current/packages/support/reflections/hasMethod.html",pathLocale:"/",extraFields:["Determine if target has method"]},{title:"Has Prototype Property",headers:[],path:"/archive/current/packages/support/reflections/hasPrototypeProperty.html",pathLocale:"/",extraFields:['Determine if "prototype" property exists.']},{title:"Is Constructor",headers:[],path:"/archive/current/packages/support/reflections/isConstructor.html",pathLocale:"/",extraFields:["Determine if value is a constructor."]},{title:"Is Key Safe",headers:[{level:2,title:"isKeySafe",slug:"iskeysafe",link:"#iskeysafe",children:[]}],path:"/archive/current/packages/support/reflections/isKeySafe.html",pathLocale:"/",extraFields:["Determine if a property key is safe."]},{title:"Is Key Unsafe",headers:[],path:"/archive/current/packages/support/reflections/isKeyUnsafe.html",pathLocale:"/",extraFields:["Determine if a property key is unsafe."]},{title:"Is Subclass",headers:[],path:"/archive/current/packages/support/reflections/isSubclass.html",pathLocale:"/",extraFields:["Determine if target is a subclass of another class."]},{title:"Is Subclass Or Looks Like",headers:[],path:"/archive/current/packages/support/reflections/isSubclassOrLooksLike.html",pathLocale:"/",extraFields:["Determine if target is a subclass of another class, or looks like blueprint"]},{title:"Is WeakKind",headers:[],path:"/archive/current/packages/support/reflections/isWeakKind.html",pathLocale:"/",extraFields:['Determine if object is of a "weak" kind.']},{title:"Version Disclaimer",headers:[{level:2,title:"Properties",slug:"properties",link:"#properties",children:[{level:3,title:"type (optional)",slug:"type-optional",link:"#type-optional",children:[]},{level:3,title:"label (optional)",slug:"label-optional",link:"#label-optional",children:[]}]},{level:2,title:"Extend Default Layout",slug:"extend-default-layout",link:"#extend-default-layout",children:[]},{level:2,title:"Client Config",slug:"client-config",link:"#client-config",children:[]}],path:"/archive/current/packages/vuepress-utils/components/version-disclaimer.html",pathLocale:"/",extraFields:["A simple notice container"]},{title:"Archive",headers:[{level:2,title:"Directory Structure",slug:"directory-structure",link:"#directory-structure",children:[]},{level:2,title:"Collections",slug:"collections",link:"#collections",children:[]},{level:2,title:"Archive Instance",slug:"archive-instance",link:"#archive-instance",children:[{level:3,title:"Name & Path",slug:"name-path",link:"#name-path",children:[]},{level:3,title:"Current & Next",slug:"current-next",link:"#current-next",children:[]}]},{level:2,title:"Vuepress Config File",slug:"vuepress-config-file",link:"#vuepress-config-file",children:[]},{level:2,title:"Onward",slug:"onward",link:"#onward",children:[]}],path:"/archive/current/packages/vuepress-utils/navigation/archive.html",pathLocale:"/",extraFields:["Archive Structure and Navigation"]},{title:"Last Updated",headers:[{level:2,title:"How to use",slug:"how-to-use",link:"#how-to-use",children:[]},{level:2,title:"Format",slug:"format",link:"#format",children:[]}],path:"/archive/current/packages/vuepress-utils/plugins/last-updated.html",pathLocale:"/",extraFields:["Last Updated Date Vuepress plugins"]}]},1901:function(e,t,r){r.d(t,{U:function(){return a}});const a=JSON.parse('{"base":"/ion/","lang":"en-GB","title":"Ion","description":"Ion Official Documentation","head":[["link",{"rel":"apple-touch-icon","sizes":"180x180","href":"/ion/images/icon/apple-touch-icon.png"}],["link",{"rel":"icon","type":"image/png","sizes":"32x32","href":"/ion/images/icon/favicon-32x32.png"}],["link",{"rel":"icon","type":"image/png","sizes":"16x16","href":"/ion/images/icon/favicon-16x16.png"}],["link",{"rel":"manifest","href":"/ion/site.webmanifest"}]],"locales":{}}')},7961:function(e,t,r){r.d(t,{K:function(){return a}});const a=JSON.parse('{"repo":"aedart/ion","colorMode":"light","logo":"/images/icon/apple-touch-icon.png","editLink":true,"editLinkText":"Edit page","docsRepo":"https://github.com/aedart/ion","docsBranch":"main","docsDir":"docs","lastUpdated":true,"lastUpdatedText":"Last Updated","navbar":[{"text":"Packages","link":"/archive/current/packages/"},{"text":"Archive","link":"/archive","children":[{"text":"next","link":"/archive/next"},{"text":"current","link":"/archive/current"}]},{"text":"Changelog","link":"https://github.com/aedart/ion/blob/main/CHANGELOG.md"}],"sidebar":{"/archive/next":[{"text":"Version 1.x","collapsible":true,"children":["/archive/next/"]}],"/archive/current":[{"text":"Version 0.x","collapsible":true,"children":["/archive/current/","/archive/current/upgrade-guide","/archive/current/contribution-guide","/archive/current/security","/archive/current/code-of-conduct","/archive/current/origin"]},{"text":"Packages","collapsible":true,"children":["/archive/current/packages/",{"text":"Contracts","collapsible":true,"children":["/archive/current/packages/contracts/","/archive/current/packages/contracts/install"]},{"text":"Support","collapsible":true,"children":["/archive/current/packages/support/","/archive/current/packages/support/install",{"text":"Arrays","collapsible":true,"children":["/archive/current/packages/support/arrays/","/archive/current/packages/support/arrays/includesAll","/archive/current/packages/support/arrays/includesAny","/archive/current/packages/support/arrays/isArrayLike","/archive/current/packages/support/arrays/isConcatSpreadable","/archive/current/packages/support/arrays/isSafeArrayLike","/archive/current/packages/support/arrays/isTypedArray","/archive/current/packages/support/arrays/merge"]},{"text":"Concerns","collapsible":true,"children":["/archive/current/packages/support/concerns/","/archive/current/packages/support/concerns/prerequisites","/archive/current/packages/support/concerns/concernClass","/archive/current/packages/support/concerns/usage","/archive/current/packages/support/concerns/aliases","/archive/current/packages/support/concerns/conflictResolution","/archive/current/packages/support/concerns/booting","/archive/current/packages/support/concerns/hooks","/archive/current/packages/support/concerns/edgeCases","/archive/current/packages/support/concerns/jsdoc"]},{"text":"Exceptions","collapsible":true,"children":["/archive/current/packages/support/exceptions/","/archive/current/packages/support/exceptions/configureCustomError","/archive/current/packages/support/exceptions/configureStackTrace","/archive/current/packages/support/exceptions/getErrorMessage","/archive/current/packages/support/exceptions/customErrors"]},{"text":"Meta","collapsible":true,"children":["/archive/current/packages/support/meta/","/archive/current/packages/support/meta/prerequisites","/archive/current/packages/support/meta/supported","/archive/current/packages/support/meta/setAndGet","/archive/current/packages/support/meta/inheritance","/archive/current/packages/support/meta/outsideChanges","/archive/current/packages/support/meta/tc39","/archive/current/packages/support/meta/targetMeta"]},{"text":"Mixins","collapsible":true,"children":["/archive/current/packages/support/mixins/","/archive/current/packages/support/mixins/newMixin","/archive/current/packages/support/mixins/apply","/archive/current/packages/support/mixins/instanceof","/archive/current/packages/support/mixins/inheritance","/archive/current/packages/support/mixins/onward"]},{"text":"Object","collapsible":true,"children":["/archive/current/packages/support/objects/","/archive/current/packages/support/objects/forget","/archive/current/packages/support/objects/forgetAll","/archive/current/packages/support/objects/get","/archive/current/packages/support/objects/has","/archive/current/packages/support/objects/hasAll","/archive/current/packages/support/objects/hasAny","/archive/current/packages/support/objects/hasUniqueId","/archive/current/packages/support/objects/isCloneable","/archive/current/packages/support/objects/isPopulatable","/archive/current/packages/support/objects/isset","/archive/current/packages/support/objects/merge","/archive/current/packages/support/objects/populate","/archive/current/packages/support/objects/set","/archive/current/packages/support/objects/uniqueId"]},{"text":"Reflections","collapsible":true,"children":["/archive/current/packages/support/reflections/","/archive/current/packages/support/reflections/assertHasPrototypeProperty","/archive/current/packages/support/reflections/classLooksLike","/archive/current/packages/support/reflections/classOwnKeys","/archive/current/packages/support/reflections/getAllParentsOfClass","/archive/current/packages/support/reflections/getClassPropertyDescriptor","/archive/current/packages/support/reflections/getClassPropertyDescriptors","/archive/current/packages/support/reflections/getConstructorName","/archive/current/packages/support/reflections/getNameOrDesc","/archive/current/packages/support/reflections/getParentOfClass","/archive/current/packages/support/reflections/hasAllMethods","/archive/current/packages/support/reflections/hasMethod","/archive/current/packages/support/reflections/hasPrototypeProperty","/archive/current/packages/support/reflections/isConstructor","/archive/current/packages/support/reflections/isKeySafe","/archive/current/packages/support/reflections/isKeyUnsafe","/archive/current/packages/support/reflections/isSubclass","/archive/current/packages/support/reflections/isSubclassOrLooksLike","/archive/current/packages/support/reflections/isWeakKind"]},{"text":"Misc","collapsible":true,"children":["/archive/current/packages/support/misc/","/archive/current/packages/support/misc/descTag","/archive/current/packages/support/misc/empty","/archive/current/packages/support/misc/isKey","/archive/current/packages/support/misc/isPrimitive","/archive/current/packages/support/misc/isPropertyKey","/archive/current/packages/support/misc/isset","/archive/current/packages/support/misc/mergeKeys","/archive/current/packages/support/misc/toWeakRef"]}]},{"text":"Vuepress Utils","collapsible":true,"children":["/archive/current/packages/vuepress-utils/","/archive/current/packages/vuepress-utils/install",{"text":"Navigation","collapsible":true,"children":["/archive/current/packages/vuepress-utils/navigation/archive"]},{"text":"Plugins","collapsible":true,"children":["/archive/current/packages/vuepress-utils/plugins/last-updated"]},{"text":"Components","collapsible":true,"children":["/archive/current/packages/vuepress-utils/components/version-disclaimer"]}]},"/archive/current/packages/xyz/"]}]},"locales":{"/":{"selectLanguageName":"English"}},"colorModeSwitch":true,"selectLanguageText":"Languages","selectLanguageAriaLabel":"Select language","sidebarDepth":2,"contributors":true,"contributorsText":"Contributors","notFound":["There\'s nothing here.","How did we get here?","That\'s a Four-Oh-Four.","Looks like we\'ve got some broken links."],"backToHome":"Take me home","openInNewWindow":"open in new window","toggleColorMode":"toggle color mode","toggleSidebar":"toggle sidebar"}')},2950:function(e,t,r){r.d(t,{A:function(){return c}});var a=r(641),s=r(6167),i=r(3456),l=r(6285),c=(0,a.pM)({__name:"LastUpdatedPlugin",setup(e){return(0,a.sV)((()=>{var e,t,r,s;const c=(0,i.BV)(),n=null!=(t=null==(e=c.value.git)?void 0:e.updatedTime)?t:null;if(!n)return;let o=null!=(r=c.value.lastUpdatedDateFormat)?r:"yyyy-LL-dd HH:mm:ss",p=null!=(s=c.value.lastUpdatedDateOptions)?s:{},u=l.c9.fromMillis(n,p).toFormat(o);(0,a.dY)((()=>{var e;const t=document.querySelector(".last-updated > span.meta-item-info");if(!t)return void console.warn("Unable to find .last-updated DOM element");t.innerHTML=u;let r=null!=(e=l.c9.fromMillis(n).toRelative())?e:"";t.setAttribute("title",r)}))})),(e,t)=>((0,a.uX)(),(0,a.Wv)(s.A))}})}},function(e){e.O(0,[170,4475],(function(){return 8731,e(e.s=8731)})),e.O()}]); \ No newline at end of file diff --git a/assets/js/apply.html.e3961839.js b/assets/js/apply.html.e3961839.js new file mode 100644 index 00000000..6f424d38 --- /dev/null +++ b/assets/js/apply.html.e3961839.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[6262],{5126:function(n,s,a){a.r(s),a.d(s,{comp:function(){return p},data:function(){return c}});var e=a(641);const i=[(0,e.Fv)('To apply one or more mixins, use the mix()
function and call width()
with the mixins you wish to apply to a superclass.
import { mix } from "@aedart/support/mixins";\nimport {\n RectangleMixin,\n DescMixin\n} from "@acme/mixins";\n\nclass Box extends mix().with(\n RectangleMixin,\n DescMixin\n) {\n // ...remaining not shown...\n}\n
To extend a superclass and apply mixins onto it, pass the superclass as argument for the mix()
function.
class Shape {\n // ...not shown...\n}\n\nclass Box extends mix(Shape).with(\n RectangleMixin,\n DescMixin\n) {\n // ...remaining not shown...\n}\n
Note
By default, if you do not provide mix()
with a superclass, an empty class is automatically created. It is the equivalent of the following:
class Box extends mix(class {}).with(\n MyMixinA,\n MyMixinB,\n MyMixinC,\n) {\n // ...\n}\n
The following illustrates a possible archive structure of your documentation. Notice the "current" and "next" directories. These two directories are essential for the Archive
component. Their names can be configured (shown later). Each of the directories contains a collection of pages.
/.vuepress\n /my_archive\n index.ts\n v3x.ts\n v4x.ts\n v5x.ts\n ...\n client.ts\n config.ts\n\n/my_archive\n /current\n README.md\n ...\n /next\n README.md\n ...\n /v4x\n README.md\n ...\n /v3x\n README.md\n ...\n README.md\n
Each Archive
component is dependent on having its structure defined by means of PagesCollection
components. As such, to represent the "current" collection, you must create a new PagesCollection
instance. Use the static make()
method to create a new instance. It accepts 3 arguments:
name: string
Name or title of the collection.path: string
The relative path in the archive to the collection.pages: SidebarConfigArray = []
An array of pages or group of pages. Each page's path is relative to the collection's path.// E.g. inside /.vuepress/my_archive/v5x.ts\nimport {PagesCollection} from "@aedart/vuepress-utils/navigation";\n\nexport default PagesCollection.make('v5.x', '/v5x', [\n {\n text: 'Version 5.x',\n collapsible: true,\n children: [\n '',\n 'contribution-guide',\n 'security',\n 'code-of-conduct',\n ]\n },\n {\n text: 'Packages',\n collapsible: true,\n children: [\n 'packages/',\n\n // ...remaining not shown here...\n ]\n },\n\n // ...etc\n]);\n
PagesCollection `path`
The path
argument of a pages collection will automatically be changed, by the Archive
component, if the collection is marked as the "current" or "next" collection (covered in next section).
Once you have your "current" and "next" collections defined, you can create a new Archive
instance. Use the static make()
method to create a new instance. It accepts 3 arguments:
current: PagesCollection
The collection to be marked as the "current".next: PagesCollection
The collection to be marked as the "next".collections: PagesCollection[] = []
Array of all available collections, including "next" and "current".// E.g. inside /.vuepress/my_archive/index.ts\nimport {PagesCollection} from "@aedart/vuepress-utils/contracts";\nimport {Archive} from "@aedart/vuepress-utils/navigation";\nimport v3x from "./v3x.ts";\nimport v4x from "./v4x.ts";\nimport v5x from "./v5x.ts";\nimport v6x from "./v6x.ts";\n\n// Defined the "current" colelction \nconst CURRENT: PagesCollection = v5x;\n\n// Defined the "next" colelction\nconst NEXT: PagesCollection = v6x;\n\n// Define all collections... next and current should also be part of this...\nconst ALL: PagesCollection[] = [\n NEXT,\n CURRENT,\n v4x,\n v3x,\n // ... etc\n];\n\n// Finally, create and export your archive with "current" and "next" \nconst archive = Archive.make(CURRENT, NEXT, ALL);\narchive.path = '/my_archive';\n\nexport default archive;\n
As shown in the previous example, the archive's path was set to /my_archive
by explicitly setting the path
property. You can do the same for its name:
// ...previous not shown ... \nconst archive = Archive.make(CURRENT, NEXT, ALL);\narchive.name = 'Good old stuff';\narchive.path = '/old_stuff';\n
WARNING
Your archive's directory structure must match the specified path
or vuepress will not be able to find it and display a "404 Not Found".
/old_stuff\n /current\n README.md\n ...\n /next\n README.md\n ...\n ...\n
Whenever a collection is marked as "current" or "next", its path
is automatically changed to /current
or /next
. This means that the full path of those collections will be the archive's path
+ current or next, e.g.
/archive/current
/archive/next
To change these paths, specify the currentPath
and nextPath
properties in your Archive
instance.
archive.currentPath = '/live'\narchive.nextPath = '/upcoming'\n
WARNING
When you change the "current" and "next" paths in your archive, then the directory structure MUST reflect these names. From the above example, the archive's directory structure should now be the following:
/my_archive\n /live\n README.md\n ...\n /upcoming\n README.md\n ...\n ...\n
You may also change the labels for "current" and "next", in a similar way as for changing their paths.
archive.currentLabel = 'Live'\narchive.nextLabel = 'What\\'s Next?'\n
import {defineUserConfig} from 'vuepress';\nimport defaultTheme from "@vuepress/theme-default"\nimport archive from './my_archive'\n\nexport default defineUserConfig({\n \n // ...other settings not shown...\n\n theme: defaultTheme({\n \n // ... other theme settings not shown ...\n \n navbar: [\n archive.asNavigationItem(),\n ],\n\n sidebar: archive.sidebarConfiguration()\n }),\n});\n
Use can review the source code and configuration of this site, as a complete example of how the Archive
component can be used.
import { assertHasPrototypeProperty } from '@aedart/support/reflections';\n\nassertHasPrototypeProperty({ __proto__: null }); // TypeError\n
class ContactsApi extends AbstractConcern {\n get users() { /* ...not shown here... */}\n}\n\n@use(ContactsApi) // Concern is NOT instantiated\nclass UsersRegistry {}\n\nconst users = (new UsersRegistry()).users; // Concern is instantiated\n
You can use the bootConcerns()
utility to manually boot concerns. It accepts the following arguments:
instance: object|Owner
- The target class instance that uses the concerns....concerns: ConcernConstructor[]
- List of concern classes to instantiate (aka. boot).import { use, bootConcerns } from "@aedart/support/concerns";\n\n@use(\n ConcernA,\n ConcernB,\n ConcernC,\n)\nclass Target {\n constructor() {\n bootConcerns(this, ConcernA, ConcernB);\n }\n}\n\nconst instance = new Target(); // ConcernA and ConcernB are instantiated\n
WARNING
If you attempt to boot a concern that has already been booted, a BootError
will be thrown!
To determine if a concern has already been booted, use the concern container's hasBooted()
method.
import {\n getContainer,\n bootConcerns\n} from "@aedart/support/concerns";\n\nclass Record extends ApiService {\n constructor() {\n super();\n \n if (!getContainer(this).hasBooted(ApiConnection)) {\n bootConcerns(this, ApiConnection);\n }\n }\n}\n
If you wish to boot all concerns, use the bootAllConcerns()
utility.
import { use, bootAllConcerns } from "@aedart/support/concerns";\n\n@use(\n ConcernA,\n ConcernB,\n ConcernC,\n)\nclass Target {\n constructor() {\n bootAllConcerns(this);\n }\n}\n\nconst instance = new Target(); // All concerns are initialised\n
classLooksLike()
accepts the following arguments:
target: object
- the target class object.blueprint: ClassBlueprint
- a blueprint that defines the expected members of a class (see Class Blueprint for details.).import { classLooksLike } from '@aedart/support/reflections';\n\nclass A {}\n\nclass B {\n foo() {}\n}\n\nconst blueprint = { members: [ 'foo' ] };\n\nclassLooksLike(A, blueprint); // false\nclassLooksLike(B, blueprint); // true\n
The class "blueprint" is an object that defines the expected members (property keys) of a target class. All defined members must exist in target class' prototype, before the classLooksLike()
returns true
.
You can specify either or both of the following properties in a class blueprint object:
members: PropertyKey[]
- (optional) Properties or methods expected to exist in class' prototype.staticMembers: PropertyKey[]
- (optional) Properties or methods expected to exist in class as static members.Note: If you do not specify either members
or staticMembers
, then a TypeError
is thrown.
class A {\n foo() {}\n\n bar() {}\n}\n\nclass B {\n foo() {}\n \n static bar() {}\n}\n\nconst blueprint = { members: [ 'foo' ], staticMembers: [ 'bar' ] };\n\nclassLooksLike(A, blueprint); // false\nclassLooksLike(B, blueprint); // true\n
classLooksLike()
traverses target class' prototype chain. This means that you can compare a subclass against a blueprint and inherited members will automatically be included in the check.
class A {\n foo() {}\n}\n\nclass B extends A {\n bar() {}\n}\n\nconst blueprint = { members: [ 'foo', 'bar' ]};\n\nclassLooksLike(A, blueprint); // false\nclassLooksLike(B, blueprint); // true\n
Returns property keys that are defined target class's prototype. It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target classrecursive: boolean = false
- (optional) If true
, then target's prototype chain is traversed and all property keys are returned.import { classOwnKeys } from '@aedart/support/reflections';\n\nclass A {\n foo() {}\n}\n\nclass B extends A {\n get bar() {}\n}\n\nclassOwnKeys(B); // [ 'constructor', 'bar' ]\nclassOwnKeys(B, true); // [ 'constructor', 'foo', 'bar' ]\n
Caution
classOwnKeys()
throws TypeError
if target does not have a prototype
property.
The classOwnKeys()
function does not return static members of a target class.
Be nice... be fair! Writing free Open Source software often means spending time developing after-hours, and in weekends. Chances are pretty good that you yourself are involved in one or many Open Source projects and already know how time-consuming it can be. In any case, the following constitutes the code of conduct used by Ion:
Participants can be subject to consequences, if the code of conduct is not upheld. Such consequences include, but not limited to, warnings, and banning from further participation.
',5)],c={};var i=(0,o(6262).A)(c,[["render",function(e,n){return(0,t.uX)(),(0,t.CE)("div",null,a)}]]);const r=JSON.parse('{"path":"/archive/current/code-of-conduct.html","title":"Code of Conduct","lang":"en-GB","frontmatter":{"description":"Code of Conduct - Ion","sidebarDepth":0},"headers":[{"level":2,"title":"Consequences","slug":"consequences","link":"#consequences","children":[]}],"git":{"updatedTime":1679232326000,"contributors":[{"name":"Alin Eugen Deac","email":"aedart@gmail.com","commits":1}]},"filePathRelative":"archive/current/code-of-conduct.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')}}]); \ No newline at end of file diff --git a/assets/js/concernClass.html.392f500d.js b/assets/js/concernClass.html.392f500d.js new file mode 100644 index 00000000..2a0c5d5f --- /dev/null +++ b/assets/js/concernClass.html.392f500d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[9628],{6482:function(n,s,a){a.r(s),a.d(s,{comp:function(){return R},data:function(){return P}});var e=a(641);const t=(0,e.Lk)("h1",{id:"concern-class",tabindex:"-1"},[(0,e.Lk)("a",{class:"header-anchor",href:"#concern-class"},[(0,e.Lk)("span",null,"Concern Class")])],-1),o=(0,e.Lk)("p",null,"This chapter shows how to create a new concern class.",-1),c={class:"table-of-contents"},i=(0,e.Fv)('AbstractConcern
To create a new concern class, you can inherit from the AbstractConcern
class.
import { AbstractConcern } from "@aedart/support/concerns";\n\nclass MyConcern extends AbstractConcern {\n \n get message() { /* ...not shown */ }\n set message(message) { /* ...not shown */ }\n \n foo() { /* ...not shown */ }\n \n [MY_SYMBOL]() { /* ...not shown */ }\n}\n
Note
Private methods and properties are NEVER "aliased" into a target class.
Note
Static methods and properties are NOT "aliased" into target class.
At the moment, such a feature is not supported by the concerns' mechanism.This may become available in the future, but presently you SHOULD NOT rely on static members becoming available for aliasing.
class A {\n foo = 'bar';\n}\n
becomes like the following, after it has been transpiled:
class A {\n constructor() {\n this.foo = 'bar';\n }\n}\n
class A {\n #foo = 'bar';\n \n get foo() {\n return this.#foo\n }\n\n set foo(v) {\n this.#foo = v;\n }\n}\n
import { AbstractConcern, PROVIDES } from "@aedart/support/concerns";\n\nclass MyConcern extends AbstractConcern {\n \n get message() { /* ...not shown */ }\n set message(message) { /* ...not shown */ }\n foo() { /* ...not shown */ }\n [MY_SYMBOL]() { /* ...not shown */ }\n \n static [PROVIDES]() {\n // Make "message" and "foo" available for aliasing...\n return [\n 'message',\n 'foo'\n ];\n }\n}\n
class ConcernsPeople extends AbstractConcern {\n with(value) {\n // ...not shown here...\n\n return this.concernOwner;\n }\n}\n\n@use(ConcernsPeople)\nclass Invitation {\n invite() { /* ...not shown here... */ }\n}\n\nconst party = new Invitation();\nparty\n .with('Anna')\n .with('Anders')\n .with('Ulla')\n .with('Jimmy')\n .invite();\n
Should you require initialisation logic, then you can overwrite the constructor
in your concern class. A concern's constructor is only given the target class instance as argument.
class Recording extends AbstractConcern {\n constructor(owner) {\n super(owner);\n \n // ...perform your initialisation here...\n }\n}\n
configureCustomError
Configures a custom error by automatically setting the error's name
property to the class' constructor name.
configureCustomError
() accepts the following arguments:
error: Error
- the custom error instancecaptureStackTrace: boolean = false
(optional) Captures and sets error's stack trace¹.import { configureCustomError } from "@aedart/support/exceptions";\n\nclass MyError extends Error {\n constructor(message, options) {\n super(message, options);\n\n configureCustomError(this);\n }\n}\n
The function accepts an Error
as argument.
import { configureStackTrace } from "@aedart/support/exceptions";\n\nclass MyError extends Error {\n constructor(message, options) {\n super(message, options);\n\n configureStackTrace(this);\n }\n}\n
A concern class may ONLY occur once in a target class' prototype chain. This is a core feature of the concerns mechanism and cannot be circumvented. However, sometimes you may find yourself in situations where different injected concern classes define the same property or method name. When this happens an AliasConflictError
is thrown.
class Label extends AbstractConcern {\n get name() { /* ...not shown.. */ }\n set name(v) { /* ...not shown.. */ }\n}\n\nclass Category extends AbstractConcern {\n get name() { /* ...not shown.. */ }\n set name(v) { /* ...not shown.. */ }\n}\n\n@use(\n Label,\n Category // AliasConflictError: Alias "name" for property ...\n)\nclass Battery {}\n
To resolve the previous shown naming conflict, you can specify custom "aliases" when injecting a concern class, via an injection configuration object.
// ... Label and Category concerns not shown ...\n\n@use(\n Label,\n {\n concern: Category,\n aliases: {\n 'name': 'category' // Alias Category's "name" property as "category"\n }\n }\n)\nclass Battery {}\n\nconst instance = new Battery();\ninstance.name = 'AAA';\ninstance.category = 'Rechargeable';\n
The aliases
option is key-value record, where;
To prevent a concern class from defining any aliases inside a target class, set the allowAliases
option to false
.
import { getConcernsContainer } from "@aedart/support/concerns";\n\n@use(\n Label,\n {\n concern: Category,\n allowAliases: false // Category's "name" is NOT aliased in target\n }\n)\nclass Battery {}\n\nconst instance = new Battery();\ninstance.name = 'AA';\n\n// Interact with Category concern to set "name"\ngetConcernsContainer(instance).get(Category).name = 'Rechargeable';\n
You can also use a shorthand version to specify a concern injection configuration, via an array. The first array value must always be the concern class that must be injected. The second value can either be an aliases
object, or boolean value for setting the allowAliases
option.
@use(\n Label,\n [Category, {\n 'name': 'category'\n }]\n)\nclass Battery {}\n
And to prevent a concern from defining aliases in a target:
@use(\n Label,\n [Category, false]\n)\nclass Battery {}\n
When time permits it, I will review your issue and take action upon it.
AbstractClassError
The AbstractClassError
is intended to be thrown whenever an abstract class is attempted instantiated directly.
import { AbstractClassError } from "@aedart/support/exceptions";\n\n/**\n * @abstract\n */\nclass MyAbstractClass {\n constructor() {\n if (new.target === MyAbstractClass) {\n throw new AbstractClassError(MyAbstractClass);\n }\n }\n}\n\nconst instance = new MyAbstractClass(); // AbstractClassError\n
LogicalError
To be thrown whenever there is an error in the programming logic.
',5),c={href:"https://www.php.net/manual/en/class.logicexception",target:"_blank",rel:"noopener noreferrer"},p=(0,e.Lk)("code",null,"LogicException",-1),l=(0,e.Fv)('import { LogicalError } from "@aedart/support/exceptions";\n\nfunction print(person) {\n if (printer === undefined) {\n throw new LogicalError('Printer is missing, unable to print people');\n }\n}\n
descTag
Return the default string description of an object.
import { descTag } from '@aedart/support/misc';\n\ndescTag('foo'); // [object String]\ndescTag(3); // [object Number]\ndescTag([1, 2, 3]); // [object Array]\ndescTag(true); // [object Boolean]\n// ... etc\n
The method is a shorthand for the following:
Object.prototype.toString.call(/* your value */);\n
It is not possible to define a property's getter and setter methods in separate concerns, and thereafter use them in a target class. Despite serving different purposes, the getter and setter share the same property name and are therefore treated as being one and the same property key. The following example will therefore always lead to an AliasConflictError
being thrown.
import { AbstractConcern, use } from "@aedart/support/concerns";\n\nclass A extends AbstractConcern {\n get title() { /* ...not shown.. */ }\n}\n\nclass B extends AbstractConcern {\n set title(value) { /* ...not shown.. */ }\n}\n\n@use(\n A,\n B // AliasConflictError - "title" property from A!\n)\nclass Person {}\n
The concerns mechanism will never overwrite existing methods or properties inside a target class - not even when those methods or properties are inherited from a parent class.
import { AbstractConcern, use } from "@aedart/support/concerns";\n\nclass Connection extends AbstractConcern {\n driver() {\n return 'special';\n }\n}\n\nclass Api {\n driver() {\n return 'default';\n }\n}\n\n@user(Connection) // driver() is NOT aliased - method inherited from Api class!\nclass SailBoat extends Api {}\n\nconst instance = new SailBoat();\ninstance.driver(); // default\n
import { AbstractConcern, use } from "@aedart/support/concerns";\n\nclass Connection extends AbstractConcern {\n driver() {\n return 'special';\n }\n}\n\nclass Api {\n driver() {\n return 'default';\n }\n}\n\n@user(\n [Connection, {\n 'driver': 'specialDriver' // alias "driver" as "specialDriver"\n }]\n)\nclass SailBoat extends Api {\n \n // Overwrite inherited method\n driver() {\n // Invoke the "specialDriver"... \n return this.specialDriver();\n }\n}\n\nconst instance = new SailBoat();\ninstance.driver(); // special\n
A concern can use other concerns classes. However, depending on your complexity, doing so may impact performance. Consider the following example:
import { AbstractConcern, use } from "@aedart/support/concerns";\n\nclass Ping extends AbstractConcern {\n ping() {\n return 'ping';\n }\n}\n\n@use(Ping)\nclass Pong extends AbstractConcern {\n pong() {\n return 'pong';\n }\n}\n\n@use(Pong)\nclass Game {}\n\nconst instance = new Game();\n\ninstance.ping(); // ping\ninstance.pong(); // pong\n
In the above shown example, whenever the ping()
method is invoked, the call stack will be similar to the following:
Game (instance).ping() -> Pong (instance).ping() -> Ping (instance).ping()\n\n("->" represents concerns container instance)\n
In some isolated cases, this might be acceptable for you. Nevertheless, if your application makes heavy use of concerns using other concerns, then your application's overall performance could suffer. You should consider merging multiple concern classes into a single class, if it is reasonable and possible. Alternatively, you can also consider extending existing concern classes. For instance:
import { AbstractConcern, use } from "@aedart/support/concerns";\n\nclass Ping extends AbstractConcern {\n ping() {\n return 'ping';\n }\n}\n\n// Extend Ping concern...\nclass Pong extends Ping {\n pong() {\n return 'pong';\n }\n}\n\n@use(Pong)\nclass Game {}\n\nconst instance = new Game();\n\ninstance.ping(); // ping\ninstance.pong(); // pong\n
Now, whenever the ping()
method is invoked, the call stack is slightly reduced:
Game (instance).ping() -> Pong (instance).ping()\n\n("->" represents concerns container instance)\n
import { empty } from '@aedart/support/misc';\n\nempty(''); // true\nempty(false); // true\nempty(0); // true\nempty(0n); // true\nempty(NaN); // true\nempty(null); // true\nempty(undefined); // true\nempty([]); // true\nempty({}); // true\nempty(new Set()); // true\nempty(new Map()); // true\nempty(new Int8Array()); // true\n\nempty(' '); // false\nempty('a'); // false\nempty(true); // false\nempty(1); // false\nempty(1n); // false\nempty(-1); // false\nempty(Infinity); // false\nempty([ 1 ]); // false\nempty({ name: 'Jimmy' }); // false\nempty((new Set()).add('a')); // false\nempty((new Map).set('foo', 'bar')); // false\nempty(new Date()); // false\nempty(function() {}); // false\nempty(Symbol('my-symbol')); // false\n\nlet typedArr = new Int8Array(1);\ntypedArr[0] = 1;\nempty(typedArr); // false\n
import { forget } from "@aedart/support/objects";\n\nconst target = {\n a: 1234,\n b: {\n c: {\n age: 24\n }\n },\n};\n\nforget(target, 'b.c');\n\nconsole.log(target); // { a: 1234, b: {} }\n
forgetAll
Remove (deletes) all values in object, at given paths.
import { forgetAll } from "@aedart/support/objects";\n\nconst target = {\n a: 1234,\n b: {\n c: {\n age: 24\n }\n },\n};\n\nforgetAll(target, [ 'a', 'b.c.age' ]);\n\nconsole.log(target); // { b: { c: {} } }\n
import { get } from "@aedart/support/objects";\n\nconst target = {\n a: 1234,\n b: {\n c: {\n age: 24\n }\n },\n};\n\nlet age = get(target, 'b.c.age');\nconsole.log(age); // 24\n
You can also specify a default value to be returned, if the resolved value is undefined
.
const target = {\n a: 1234,\n b: {\n c: {\n age: undefined\n }\n },\n};\n\n// Returns default value...\nlet age = get(target, 'b.c.age', 20);\nconsole.log(age); // 20\n
Returns all parents of target class. It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target class.includeTarget: boolean = false
- (optional) If true
, then given target is included in the output as the first element.import { getAllParentsOfClass } from '@aedart/support/reflections';\n\nclass A {}\n\nclass B extends A {}\n\nclass C extends B {}\n\ngetAllParentsOfClass(C); // [ B, A ]\ngetAllParentsOfClass(C, true); // [ C, B, A ]\n
It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target class.key: PropertyKey
- Name of the property.import { getClassPropertyDescriptor } from '@aedart/support/reflections';\n\nclass A {\n set name(v) {}\n get name() {}\n}\n\ngetClassPropertyDescriptor(A, 'name'); // see "output"...\n
The above show example results in the given output:
const output = {\n get: function () { /* ...Not shown... */ },\n set: function (v) { /* ..Not shown... */ },\n enumerable: false,\n configurable: true\n};\n
Note
getClassPropertyDescriptor()
returns undefined
if requested key does not exist in class' prototype.
Caution
getClassPropertyDescriptor()
throws TypeError
if target does not have a prototype
property.
It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target class.recursive: boolean = false
- (optional) If true
, then target's parent prototypes are traversed. Descriptors are merged, such that the top-most class' descriptors are returned.import { getClassPropertyDescriptors } from '@aedart/support/reflections';\n\nclass A {\n set name(v) {}\n get name() {}\n bar() {}\n [MY_SYMBOL]() {}\n}\n\ngetClassPropertyDescriptors(A); // { bar: {...}, name: {...}, [MY_SYMBOL]: {...} }\n
When recursive
is set to true
, then all property descriptors are returned from the target class' prototype chain.
import { getClassPropertyDescriptors } from '@aedart/support/reflections';\n\nclass A {\n set name(v) {}\n get name() {}\n foo() {}\n [MY_SYMBOL]() {}\n}\n\nclass B extends A {\n set bar(v) {}\n get bar() {}\n}\n\ngetClassPropertyDescriptors(B, true);\n// { bar: {...}, foo: {...}, name: {...}, [MY_SYMBOL]: {...} }\n
Caution
getClassPropertyDescriptors()
throws TypeError
if target does not have a prototype
property.
Returns target class' constructor name, if available.
It accepts the following arguments:
target: ConstructorOrAbstractConstructor
- The target classdefaultValue: string|null = null
- (optional) A default string value to return if target has no constructor name.import { getConstructorName } from '@aedart/support/reflections';\n\nclass Box {}\n\ngetConstructorName(Box); // Box\ngetConstructorName(class {}); // null\ngetConstructorName(class {}, 'MyBox'); // MyBox\n
import { getErrorMessage } from "@aedart/support/exceptions";\n\ntry {\n throw new Error('Something went wrong!');\n} catch(e) {\n const msg = getErrorMessage(e, 'unknown error'); // Something went wrong! \n}\n\n// ---------------------------------------------------------------------------\n\ntry {\n throw 'Something went wrong!';\n} catch(e) {\n const msg = getErrorMessage(e, 'unknown error'); // unknown error \n}\n
import { getNameOrDesc } from '@aedart/support/reflections';\n\nclass ApiService {}\n\ngetNameOrDesc(ApiService); // ApiService\ngetNameOrDesc(class {}); // [object Function]\n
Returns the parent class of given target class, or null
if class does not have a parent.
import { getParentOfClass } from '@aedart/support/reflections';\n\nclass A {}\n\nclass B extends A {}\n\nclass C extends B {}\n\ngetParentOfClass(A); // null\ngetParentOfClass(B); // A\ngetParentOfClass(C); // B\n
import { has } from "@aedart/support/objects";\n\nconst target = {\n a: 1234,\n b: {\n c: {\n age: 24\n }\n },\n};\n\nlet result = has(target, 'b.c.age');\nconsole.log(result); // true\n
import { hasAll } from "@aedart/support/objects";\n\nconst mySymbol = Symbol('my-symbol');\nconst target = {\n a: 1234,\n b: {\n name: 'Sven',\n c: {\n age: 24,\n [mySymbol]: true\n }\n },\n d: [\n { name: 'Jane'},\n { name: 'Ashley'},\n ],\n};\n\nconst paths = [\n 'a',\n 'b.name',\n 'b.c.age',\n ['b', 'c', mySymbol],\n 'd[0]',\n 'd[1].name',\n];\n\nlet result = hasAll(target, paths);\nconsole.log(result); // true\n
Determine if given target object contains all given methods.
It accepts the following arguments:
target: object
- The target....methods: PropertyKey[]
- Names of the methods to check for.import { hasAllMethods } from '@aedart/support/reflections';\n\nconst a = {\n foo: () => { /* ...not shown... */ },\n bar: () => { /* ...not shown... */ },\n}\n\nhasAllMethods(a, 'foo', 'bar'); // true\nhasAllMethods(a, 'foo', 'bar', 'zar'); // false\n
hasAny
Determine if any paths are properties of given object.
import { hasAny } from "@aedart/support/objects";\n\nconst target = {\n a: 1234,\n b: {\n name: 'Sven',\n c: {\n age: 24\n }\n }\n};\n\nconst paths = [\n 'z', // does not exist\n 'b.c.name', // does not exist\n 'b.c.age', // exist\n];\n\nlet result = hasAny(target, paths);\nconsole.log(result); // true\n
Determine if given target object contains method.
It accepts the following arguments:
target: object
- The target.method: PropertyKey
- Name of the method to check for.import { hasMethod } from '@aedart/support/reflections';\n\nconst a = {\n foo: () => { /* ...not shown... */ },\n bar: () => { /* ...not shown... */ },\n}\n\nhasMethod(a, 'foo'); // true\nhasMethod(a, 'bar'); // true\nhasMethod(a, 'zar'); // false\n
Determines if object has a prototype
property defined and that it is not null
or undefined
.
import { hasPrototypeProperty } from '@aedart/support/reflections';\n\nhasPrototypeProperty(null); // false\nhasPrototypeProperty(Object.create(null)); // false\nhasPrototypeProperty({ __proto__: undefined }); // false\nhasPrototypeProperty({ prototype: null }); // false\nhasPrototypeProperty(() => true); // false\n\nhasPrototypeProperty(Object.create({ prototype: {} })); // true\nhasPrototypeProperty({ __proto__: function() {} }); // true\nhasPrototypeProperty(function() {}); // true\nhasPrototypeProperty(class {}); // true\n
import { hasUniqueId } from "@aedart/support/objects";\n\nconst target = {\n name: 'Ursula'\n};\n\nconsole.log(hasUniqueId(target)); // false\n
BEFORE
RegistrationTo perform pre-registration logic, use the static BEFORE
method in your concern class. This hook method is invoked before the concern container and aliases are defined in the target class.
The method accepts the following arguments:
target: UsesConcerns
- the target class (class constructor!).import { BEFORE } from "@aedart/contracts/support/concerns";\nimport { AbstractConcern } from "@aedart/support/concerns";\nimport { isSubclass } from '@aedart/support/reflections';\n\nimport { JobHandler } from '@acme/jobs';\n\nclass RecordsJobs extends AbstractConcern {\n \n static [BEFORE](target) {\n // E.g. prevent this concern from being used by all kinds of targets...\n if (!isSubclass(target, JobHandler)) {\n throw new TypeError('RecordsJobs can only be used by JobHandler');\n }\n }\n}\n
AFTER
RegistrationTo perform post-registration logic, use the static AFTER
method in your concern class. This method is invoked after the concern container and aliases have been defined in target's prototype.
The method accepts the following arguments:
target: UsesConcerns
- the target class (class constructor!).import { AFTER } from "@aedart/contracts/support/concerns";\nimport { AbstractConcern } from "@aedart/support/concerns";\n\nimport { ApiConnection } from '@acme/api';\n\nclass RecordsJobs extends AbstractConcern {\n \n static [AFTER](target) {\n // E.g. init or setup static resources...\n ApiConnection.init();\n }\n}\n
includesAll
Determines if an array includes all values.
import { includesAll } from '@aedart/support/arrays';\n\nconst arr = [ 1, 2, 3 ];\n\nincludesAll(arr, [ 1, 2 ]); // true\nincludesAll(arr, [ 1, 4 ]); // false\n
includesAny
Determines if an array includes some values.
import { includesAny } from '@aedart/support/arrays';\n\nconst arr = [ 1, 2, 3 ];\n\nincludesAll(arr, [ 4, 2 ]); // true\nincludesAll(arr, [ 5, 5 ]); // false\n
Version | TypeScript | ECMA Script | Release | Security Fixes Until |
---|---|---|---|---|
1.x | 5.0 - ? | TBD | TBD | TBD |
0.x * | 5.0 | ES2022 | ongoing releases | until v1.x release |
*: current supported version.
TBD: "To be decided".
v0.x
Highlightsimport { use, AbstractConcern } from "@aedart/support/concerns";\n\n// A concern class...\nclass Role extends AbstractConcern {\n addRole(name) {\n /* ...not shown... */\n }\n}\n\n// Use concern in target class...\n@use(Role)\nclass User {}\n\n// Later in your application...\nconst user = new User();\nuser.addRole('maintainer');\nuser.addRole('supporter');\n
import { merge } from "@aedart/support/objects";\n\nconst a = {\n 'name': 'Alin',\n};\n\nconst b = {\n 'address': {\n 'street': 'Northern Street 1'\n },\n};\n\nconst result = merge(a, b); // { 'name': 'Alin', 'address': { 'street': '...' } }\n
import { mix, Mixin } from "@aedart/support/mixins";\n\nconst NameMixin = Mixin((superclass) => class extends superclass {\n #name;\n \n set name(value) {\n this.#name = value;\n }\n \n get name() {\n return this.#name;\n }\n});\n\nclass Item extends mix().with(\n NameMixin\n) {}\n\n// ...Later in your application\nconst item = new Item();\nitem.name = 'My Item';\n\nconsole.log(item.name); // My Item\n
import {targetMeta, getTargetMeta} from '@aedart/support/meta';\n\nclass Service {\n\n @targetMeta('desc', 'Seaches for cities')\n search() {\n // ...not shown...\n }\n}\n\nconst instance = new Service();\n\n// ...later in your application...\ngetTargetMeta(instance.search, 'desc'); // Seaches for cities\n
import {meta, getMeta} from '@aedart/support/meta';\n\n@meta('description', 'Able to search for locations')\nclass Service {}\n\ngetMeta(Service, 'description'); // Able to search for locations\n
In this context, a "concern" is a class that can be injected into a target class, by means of the use()
class decorator. The public properties and methods of the concern class are then "aliased" into the target class' prototype. In other words, "proxy" properties and methods are defined in the target class. They forward any interaction to the original properties and methods in the concern class instance.
import { use, AbstractConcern } from "@aedart/support/concerns";\n\n// A concern class...\nclass ConsolePrinter extends AbstractConcern {\n print(message) {\n console.log(message);\n }\n}\n\n// Taget class that uses a concern...\n@use(ConsolePrinter)\nclass Person {\n \n sayHi(name) {\n // Call method in concern\n this.print(`Hi ${name}`);\n }\n}\n\n// Later in your application...\nconst person = new Person();\nperson.sayHi('Atrid'); // Hi Astrid\n\nperson.print('Ho ho ho...'); // Ho ho ho...\n
Provides a decorator that is able to associate metadata with a class, its methods and properties.
import { meta, getMeta } from '@aedart/support/meta';\n\n@meta('service_alias', 'locationSearcher')\nclass Service {}\n\ngetMeta(Service, 'service_alias'); // locationSearcher\n
import { mix, Mixin } from "@aedart/support/mixins";\n\n// Define mixin\nconst NameMixin = Mixin((superclass) => class extends superclass {\n\n #name;\n \n set name(value) {\n this.#name = value;\n }\n \n get name() {\n return this.#name;\n }\n});\n\n// Apply mixin...\nclass Item extends mix().with(\n NameMixin\n) {\n // ...not shown... \n}\n\n// ...Later in your application\nconst item = new Item();\nitem.name = 'My Item';\n\nconsole.log(item.name); // My Item\n
Metadata is automatically inherited by subclasses.
import { meta, getMeta } from '@aedart/support/meta';\n\n@meta('service_alias', 'locationSearcher')\nclass Service {}\n\nclass CitySearcher extends Service {}\n\ngetMeta(CitySearcher, 'service_alias'); // locationSearcher\n
You can also overwrite the inherited metadata. The subclass that defines the metadata creates its own copy of the inherited metadata. The parent class' metadata remains untouched.
import { meta, getMeta } from '@aedart/support/meta';\n\nclass Service {\n \n @meta('search.desc', 'Searches for countries')\n search() {\n // ...not shown...\n }\n}\n\nclass CitySearcher extends Service {\n\n @meta('search.desc', 'Searches for cities')\n search() {\n // ...not shown...\n }\n}\n\nconst service = new CitySearcher();\n\ngetMeta(CitySearcher, 'search.desc'); // Searches for cities\ngetMeta(Service, 'search.desc'); // Searches for countries\n
To gain an overview of how inheritance works when applying mixins onto a superclass, consider the following example:
const MyMixin = Mixin((superclass) => class extends superclass {\n constructor(...args) {\n super(...args); // Invokes A's constructor\n }\n \n // Overwrites A's foo() method\n foo() {\n return 'zam';\n }\n\n // Overwrites A's bar() method\n bar() {\n return super.bar(); // Invoke A's bar() method\n }\n});\n\n// -------------------------------------------------------------------- //\n\nclass A {\n foo() {\n return 'foo';\n }\n \n bar() {\n return 'bar';\n }\n}\n\n// -------------------------------------------------------------------- //\n\nclass B extends mix(A).with(\n MyMixin\n) {\n constructor(...args) {\n super(...args); // Invokes MyMixin's constructor\n }\n\n // Overwrite MyMixin's foo()\n foo() {\n const msg = super.foo(); // Invoke MyMixin's bar() method\n\n return `<${msg}>`;\n }\n}\n\n// -------------------------------------------------------------------- //\n\nconst instance = new B();\n\nconsole.log(instance.foo()); // <zam>\nconsole.log(instance.bar()); // bar\n
npm install --save-peer @aedart/contracts\n
yarn add --peer @aedart/contracts\n
pnpm add --save-peer @aedart/contracts\n
npm install --save-peer @aedart/support\n
yarn add --peer @aedart/support\n
pnpm add --save-peer @aedart/support\n
npm install --save-dev @aedart/vuepress-utils\n
yarn add --dev @aedart/vuepress-utils\n
pnpm add --save-dev @aedart/vuepress-utils\n
// A regular mixin without "Mixin" decorator \nconst MixinA = (superclass) => class extends superclas {\n // ...not shown...\n};\n\n// Mixin with "Mixin" decorator\nconst MixinB = Mixin((superclass) => class extends superclass {\n // ...not shown...\n});\n\n// -------------------------------------------------------------------- //\n\nclass A {}\n\nclass B extends mix(A).with(\n MixinA,\n MixinB\n) {}\n\n// -------------------------------------------------------------------- //\n\nconst instance = new B();\n\nconsole.log(instance instanceof A); // true\nconsole.log(instance instanceof B); // true\nconsole.log(instance instanceof MixinA); // false\nconsole.log(instance instanceof MixinB); // true\n
import { isArrayLike } from '@aedart/support/arrays';\n\nisArrayLike([]); // true\nisArrayLike('abc'); // true\nisArrayLike(new String('abc')); // true\nisArrayLike({ length: 0 }); // true\nisArrayLike(new Int8Array()); // true\n\nisArrayLike({}); // false\nisArrayLike(function() {}); // false\nisArrayLike(new Boolean(true)); // false\nisArrayLike(123); // false\nisArrayLike(new Number(123)); // false\n// ...etc\n
Determines if given object is "cloneable". In this context "cloneable" means that an object implements the Cloneable
interface, and offers a clone()
method.
See @aedart/constracts/support/objects/Cloneable
for details.
import { isCloneable } from "@aedart/support/objects";\n\nclass A {};\n\nclass B {\n clone() {\n return new this();\n }\n}\n\nisCloneable(null); // false\nisCloneable([]); // false\nisCloneable({}); // false\nisCloneable(new A()); // false\nisCloneable(new B()); // true\n
import { isConcatSpreadable } from '@aedart/support/arrays';\n\nisConcatSpreadable(null); // false\nisConcatSpreadable([ 1, 2, 3 ]); // false\nisConcatSpreadable({}); // false\n\n// -------------------------------------------------------------------------\n\nconst arr = [ 1, 2, 3 ];\narr[Symbol.isConcatSpreadable] = true;\nisConcatSpreadable(arr); // true\n\n// -------------------------------------------------------------------------\n\nconst obj = {\n [Symbol.isConcatSpreadable]: true,\n\n // NOTE: length should be present, if Symbol.isConcatSpreadable\n // set to `true` However, isConcatSpreadable() does not check\n // if `length` is set!\n length: 3,\n 0: 'a',\n 1: 'b',\n 2: 'c'\n};\nisConcatSpreadable(obj); // true\n\n// ------------------------------------------------------------------------- \n\nclass A {}\nclass B {\n [Symbol.isConcatSpreadable] = false;\n}\nisConcatSpreadable(new A()); // false\nisConcatSpreadable(new B()); // true\n
import { isConstructor } from "@aedart/support/reflections";\n\nisConstructor(null); // false\nisConstructor({}); // false\nisConstructor([]); // false\nisConstructor(function() {}); // true\nisConstructor(() => {}); // false\nisConstructor(Array); // true\nisConstructor(class {}); // true\n
Acknowledgement
',2),u={href:"https://github.com/zloirock/core-js#function-iscallable-isconstructor-",target:"_blank",rel:"noopener noreferrer"},l=(0,t.Lk)("em",null,"License MIT",-1),r={};var k=(0,a(6262).A)(r,[["render",function(n,s){const a=(0,t.g2)("ExternalLinkIcon");return(0,t.uX)(),(0,t.CE)("div",null,[o,(0,t.Lk)("p",null,[(0,t.eW)("Based on the "),(0,t.Lk)("a",e,[(0,t.eW)("TC39 "),c,(0,t.bF)(a)]),(0,t.eW)(" proposal, the "),p,(0,t.eW)(" can determine if given argument is a constructor.")]),i,(0,t.Lk)("p",null,[(0,t.eW)("The source code of the above shown methods is heavily inspired by Denis Pushkarev's Core-js implementation of the "),(0,t.Lk)("a",u,[(0,t.eW)("Function.isCallable / Function.isConstructor"),(0,t.bF)(a)]),(0,t.eW)(" proposal ("),l,(0,t.eW)(").")])])}]]);const d=JSON.parse('{"path":"/archive/current/packages/support/reflections/isConstructor.html","title":"Is Constructor","lang":"en-GB","frontmatter":{"title":"Is Constructor","description":"Determine if value is a constructor.","sidebarDepth":0},"headers":[],"git":{"updatedTime":1709288721000,"contributors":[{"name":"alin","email":"alin@rspsystems.com","commits":1}]},"filePathRelative":"archive/current/packages/support/reflections/isConstructor.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')}}]); \ No newline at end of file diff --git a/assets/js/isKey.html.e4b8f164.js b/assets/js/isKey.html.e4b8f164.js new file mode 100644 index 00000000..63add13e --- /dev/null +++ b/assets/js/isKey.html.e4b8f164.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[6529],{9845:function(n,s,a){a.r(s),a.d(s,{comp:function(){return l},data:function(){return u}});var t=a(641);const e={id:"iskey",tabindex:"-1"},p={class:"header-anchor",href:"#iskey"},o=(0,t.Lk)("code",null,"isKey",-1),c=(0,t.Fv)('import { isKey } from '@aedart/support/misc';\n\nisKey('foo'); // true\nisKey(12); // true\nisKey(Symbol('my-symbol')); // true\nisKey([ 'a', 'b.c', Symbol('my-other-symbol')]); // true\n\nisKey(true); // false\nisKey([]); // false\nisKey(null); // false\nisKey(undefined); // false\nisKey(() => true); // false\n
import { isKeySafe } from '@aedart/support/reflections';\n\nisKeySafe('name'); // true\nisKeySafe('length'); // true\nisKeySafe('constructor'); // true\nisKeySafe('__proto__'); // false\n
Determines if a property key is considered "unsafe".
import { isKeyUnsafe } from '@aedart/support/reflections';\n\nisKeyUnsafe('name'); // false\nisKeyUnsafe('length'); // false\nisKeyUnsafe('constructor'); // false\nisKeyUnsafe('__proto__'); // true\n
Note
Behind the scene, the isKeyUnsafe()
function matches the given key against values from the predefined DANGEROUS_PROPERTIES
list, which is defined in the @aedart/contracts/support/objects
submodule;
import { DANGEROUS_PROPERTIES } from "@aedart/contracts/support/objects";\n
Determines if given object is "populatable". Here, "populatable" means that an object implements the Populatable
interface, and offers a populate()
method.
See @aedart/constracts/support/objects/Populatable
for details.
import { isPopulatable } from "@aedart/support/objects";\n\nclass A {};\n\nclass B {\n populate(data) {\n // ...not shown here...\n \n return this;\n }\n}\n\nisPopulatable(null); // false\nisPopulatable([]); // false\nisPopulatable({}); // false\nisPopulatable(new A()); // false\nisPopulatable(new B()); // true\n
import { isPrimitive } from '@aedart/support/misc';\n\nisPrimitive(null); // true\nisPrimitive(undefined); // true\nisPrimitive(true); // true\nisPrimitive(1); // true\nisPrimitive(1n); // true\nisPrimitive('foo'); // true\nisPrimitive(Symbol('my-symbol')); // true\n\nisPrimitive([1, 2, 3]); // false\nisPrimitive({ name: 'Rian' }); // false\nisPrimitive(function() {}); // false\n
Determine if a key a valid property key name (string, number, or symbol).
import { isPropertyKey } from '@aedart/support/misc';\n\nisPropertyKey('foo'); // true\nisPropertyKey(12); // true\nisPropertyKey(Symbol('my-symbol')); // true\n\nisPropertyKey(true); // false\nisPropertyKey(['a', 'b', 'c']); // false\nisPropertyKey(null); // false\nisPropertyKey(undefined); // false\nisPropertyKey(() => true); // false\n
import { isSafeArrayLike } from '@aedart/support/arrays';\n\nisSafeArrayLike([]); // true\nisSafeArrayLike({ length: 0 }); // true\n\nisSafeArrayLike('abc'); // false\nisSafeArrayLike(new String('abc')); // false\nisSafeArrayLike(new Int8Array()); // false\n// ...etc\n
Determine if target class is a subclass (child class) of given superclass (parent class).
It accepts the following arguments:
target: object
- The target.superclass: ConstructorOrAbstractConstructor
- The superclass.import { isSubclass } from '@aedart/support/reflections';\n\nclass A {}\n\nclass B extends A {}\n\nisSubclass({}, A); // false\nisSubclass(A, A); // false\nisSubclass(A, B); // false\n\nisSubclass(B, A); // true\n
import { isSubclassOrLooksLike } from '@aedart/support/reflections';\n\nclass A {\n foo() {}\n}\nclass B extends A {}\n\nclass C {\n foo() {}\n}\n\nisSubclassOrLooksLike(B, A, { members: [] }); // true\nisSubclassOrLooksLike(C, A, { members: [] }); // false\nisSubclassOrLooksLike(C, A, { members: [ 'foo' ] }); // true\n
import { isTypedArray } from '@aedart/support/arrays';\n\nisTypedArray(null); // false\nisTypedArray({}); // false\nisTypedArray([]); // false\nisTypedArray(new Map()); // false\n\nisTypedArray(new Int8Array()); // true\nisTypedArray(new Uint8Array()); // true\nisTypedArray(new Uint8ClampedArray()); // true\nisTypedArray(new Int16Array()); // true\nisTypedArray(new Uint16Array()); // true\nisTypedArray(new Int32Array()); // true\nisTypedArray(new Uint32Array()); // true\nisTypedArray(new Float32Array()); // true\nisTypedArray(new Float64Array()); // true\nisTypedArray(new BigInt64Array()); // true\nisTypedArray(new BigUint64Array()); // true\n
import { isWeakKind } from '@aedart/support/reflections';\n\nconst a = {};\n\nisWeakKind(null); // false\nisWeakKind(a); // false\nisWeakKind(new Map()); // false\nisWeakKind(new Set()); // false\n\nisWeakKind(new WeakRef(a)); // true\nisWeakKind(new WeakMap()); // true\nisWeakKind(new WeakSet()); // true\n
import { isset } from '@aedart/support/misc';\n\nisset('foo'); // true\nisset(''); // true\nisset(true); // true\nisset(false); // true\nisset(1234); // true\nisset(1.234); // true\nisset([]); // true\nisset({}); // true\nisset(() => true); // true\n\nisset(undefined); // false\nisset(null); // false\n
You can also determine if multiple values differ from undefined
and null
.
Note: All given values must differ from undefined
and null
, before method returns true
.
isset('foo', { name: 'Jane' }, [ 1, 2, 3 ]); // true\n\nisset('foo', null, [ 1, 2, 3 ]); // false\nisset('foo', { name: 'Jane' }, undefined); // false\n
import { isset } from "@aedart/support/objects";\n\nconst target = {\n a: 1234,\n b: {\n name: undefined,\n c: {\n age: null\n }\n },\n};\n\nconsole.log(isset(target, 'a')); // true\nconsole.log(isset(target, 'b')); // true\nconsole.log(isset(target, 'b.name')); // false\nconsole.log(isset(target, 'b.c')); // true\nconsole.log(isset(target, 'b.c.age')); // false\n
You can also check if multiple paths are set.
console.log(isset(target, 'a', 'b')); // true\nconsole.log(isset(target, 'b.c', 'b.name')); // false\nconsole.log(isset(target, 'a', 'b.name', 'b.c.age')); // false\n
import { use, AbstractConcern } from "@aedart/support/concerns";\n\n/**\n * @mixin\n * @extends AbstractConcern\n */\nclass Shield extends AbstractConcern {\n\n /**\n * Returns the armor level\n *\n * @returns {number}\n */\n get armor() {\n return 8;\n }\n\n /**\n * Throw shield towards a target\n *\n * @param {object} target\n *\n * @returns {number} Damage given to target\n */\n throw(target) {\n // target ignored here...\n return 3;\n }\n}\n\n/**\n * @mixes Shield\n */\n@use([Shield, {\n 'throw': 'fight'\n}])\nclass Monster {\n\n /**\n * Alias for {@link Shield#throw}\n *\n * @function fight\n * @param {object} target The target to throw at...\n * @return {number} Damage taken by target\n * @instance\n * @memberof Monster\n */\n\n /**\n * Do stuff...\n */\n do() {\n this.fight({});\n }\n}\n
@property
Downside: Properties and methods described via @property
are listed as "static" on the target class. Also, it is not possible to reuse existing JSDoc from your concern.
import { use, AbstractConcern } from "@aedart/support/concerns";\n\nclass Armor extends AbstractConcern {\n\n /**\n * Returns the armor level\n *\n * @returns {number}\n */\n get level() {\n return 8;\n }\n}\n\n/**\n * @property {number} armor Returns the armor level\n */\n@use([Armor, {\n 'level': 'armor'\n}])\nclass Hero {}\n
@borrows
import { use, AbstractConcern } from "@aedart/support/concerns";\n\n/**\n * @extends AbstractConcern\n */\nclass Spell extends AbstractConcern {\n \n /**\n * Cast the spell\n *\n * @name cast\n * \n * @returns {number} Damage done\n */\n cast() {\n return 7;\n }\n}\n\n/**\n * @borrows Spell#cast as damage\n */\n@use([Spell, {\n 'cast': 'damage'\n}])\nclass Mage {\n \n /**\n * @function damage\n * @return {number}\n * @instance\n * @memberof Npc\n */\n}\n
@member
Downside: This approach can be very cumbersome. Also, reuse of JSDoc is not possible.
import { use, AbstractConcern } from "@aedart/support/concerns";\n\nclass Sword extends AbstractConcern {\n\n /**\n * Returns amount of damage\n *\n * @returns {number}\n */\n get slash() {\n return 3;\n }\n\n /**\n * Returns the sword type\n *\n * @name type\n * @return {string}\n */\n get type() {\n return 'unique';\n }\n}\n\n\n@use([Sword, {\n 'slash': 'damage'\n}])\nclass Enemy {\n\n /**\n * @public\n * @member {number} damage Alias for {@link Sword#slash}\n * @memberof Enemy\n */\n\n /**\n * @public\n * @member {string} type Alias for {@link Sword#type}\n * @memberof Enemy\n */\n}\n
import {defineUserConfig} from 'vuepress';\nimport {lastUpdatedPlugin} from "@aedart/vuepress-utils/plugins";\n\nexport default defineUserConfig({\n \n // ...other settings not shown...\n \n plugins: [\n \n lastUpdatedPlugin()\n ]\n});\n
lastUpdatedPlugin({ format: 'dd-MM-yyyy HH:mm:ss' })\n
Note
The plugin uses yyyy-MM-dd HH:mm:ss ZZZZ
as default format, when none is given.
Example output: 2023-03-19 16:09:20 GMT+1
import { merge } from '@aedart/support/arrays';\n\nconst a = [ 1, 2, 3 ];\nconst b = [ 4, 5, 6 ];\nconst c = [ 7, 8, 9 ];\n\nmerge(a, b, c); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]\n
const a = { foo: 'foo' };\nconst b = { bar: 'bar' };\nconst c = { ping: 'pong' };\n\nconst result = merge([ a ], [ b, c ]);\n\nconsole.log(result[0] === a); // false\nconsole.log(result[1] === b); // false\nconsole.log(result[2] === c); // false\n
In situations when values cannot be copied via structuredClone
, an ArrayMergeError
is thrown.
const a = [ 1, 2, 3 ];\nconst b = [ function() {} ]; // A function cannot be deep copied...\n\nmerge(a, b); // ArrayMergeError\n
Example
import { merge } from "@aedart/support/objects";\n\nconst person = {\n 'name': 'Alice',\n};\n\nconst address = {\n 'address': {\n 'street': 'Somewhere Street 43'\n },\n};\n\nconst result = merge(person, address);\n\nconsole.log(result);\n
The above shown example results in a new object that looks like this:
{\n "name": "Alice",\n "address": {\n "street": "Somewhere Street 43"\n }\n}\n
const a = {\n 'foo': null,\n 'bar': Symbol('my_symbol')\n};\n\nconst b = {\n 'foo': function() {},\n};\n\nconst result = merge(a, b);\n\nconsole.log(result.foo === b.foo); // true\nconsole.log(result.bar === a.bar); // true\n
Property keys that are considered "unsafe", are never copied.
const a = {\n 'foo': 'bar'\n};\nconst b = {\n __proto__: { 'is_admin': true }\n}\n\nconst result = merge(a, b);\n\nconsole.log(result); // { 'foo': 'bar' }\nconsole.log(Reflect.has(result, '__proto__')); // false\n
merge()
supports a number of options. To specify thom, use the using()
method.
merge()\n .using({ /** option: value */ })\n .of(objA, objB, objC);\n
Note
When invoking merge()
without any arguments, an underlying objects Merger
instance is returned.
depth
The depth
option specifies the maximum merge depth.
512
A MergeError
is thrown, if the maximum depth is exceeded.
const a = {\n 'person': {\n 'name': 'Una'\n }\n};\n\nconst b = {\n 'person': { // Level 0\n 'age': 24, // Level 1\n 'address': {\n 'street': 'Somewhere Str. 654' // Level 2\n }\n }\n};\n\nconst result = merge()\n .using({\n depth: 1\n })\n .of(a, b); // MergeError - Maximum merge depth (1) has been exceeded\n
skip
skip
defines property keys that must not be merged.
It accepts an array of property keys or a callback.
const a = {\n 'person': {\n 'name': 'Ulrik'\n }\n};\n\nconst b = {\n 'person': {\n 'age': 36,\n 'address': {\n 'street': 'Nowhere Str. 12'\n }\n }\n};\n\nconst result = merge()\n .using({\n skip: [ 'age' ]\n })\n .of(a, b);\n
The above example results in the following new object:
{\n "person": {\n "name": "Ulrik",\n "address": {\n "street": "Nowhere Str. 12"\n }\n }\n}\n
Note
When specifying a list of property keys, then the depth level in which the property key is found does not matter.
You can use a callback, if you need to handle more advanced skip logic. The callback accepts the the following arguments:
key: PropertyKey
- The current property that is being processed.source: object
- The source object that contains the key.result: object
- The resulting object (relative to the current depth that is being processed).The callback MUST return a boolean value; true
if given key must be skipped, false
otherwise.
const a = {\n 'person': {\n 'name': 'Jane'\n }\n};\n\nconst b = {\n 'person': {\n 'name': 'James',\n 'address': {\n 'street': 'Sunview Palace 88'\n }\n }\n};\n\nconst b = {\n 'person': {\n 'name': 'White',\n }\n};\n\nconst result = merge()\n .using({\n skip: (key, source, result) => {\n return key === 'name'\n && source[key] !== null\n && !Reflect.has(result, key); \n }\n })\n .of(a, b);\n
The above example results in the following new object:
{\n "person": {\n "name": "Jane",\n "address": {\n "street": "Sunview Palace 88"\n }\n }\n}\n
overwriteWithUndefined
Determines if a property value should be overwritten with undefined
.
Note: By default, all values are overwritten, even when they are undefined
!
const a = { 'foo': true };\nconst b = { 'foo': undefined };\n\nmerge(a, b); // { 'foo': undefined }\n\nmerge()\n .using({ overwriteWithUndefined: false })\n .of(a, b) // { 'foo': true }\n
useCloneable
Note: By default, if an object is cloneable, then its return value from clone()
is used.
const a = { 'foo': { 'name': 'John Doe' } };\nconst b = { 'foo': {\n 'name': 'Jane Doe',\n clone() {\n return {\n 'name': 'Rick Doe',\n 'age': 26\n }\n }\n} };\n\nmerge(a, b); // { 'foo': { 'name': 'Rick Doe', 'age': 26 } }\n\nmerge()\n .using({ useCloneable: false })\n .of(a, b); // { 'foo': { 'name': 'Jane Doe', clone() {...} } }\n
mergeArrays
Note: By default, existing array values are NOT merged.
const a = { 'foo': [ 1, 2, 3 ] };\nconst b = { 'foo': [ 4, 5, 6 ] };\n\nmerge(a, b); // { 'foo': [ 4, 5, 6 ] }\n\nmerge()\n .using({ mergeArrays: true })\n .of(a, b); // { 'foo': [ 1, 2, 3, 4, 5, 6 ] }\n
callback
In situations when you need more advanced merge logic, you may specify a custom callback.
The callback is responsible for returning the value to be merged, from a given source object.
const a = {\n 'a': 1\n};\n\nconst b = {\n 'b': 2\n};\n\nconst result = merge()\n .using({\n callback: (target, next, options) => {\n const { key, value } = target;\n if (key === 'b') {\n return value + 1;\n }\n\n return value;\n }\n })\n .of(a, b); // { 'a': 1, 'b': 3 }\n
If you do not have other merge options to specify, then you can simply provide a merge callback directly as argument for the using()
method.
const result = merge()\n .using((target, next, options) => {\n const { key, value } = target;\n if (key === 'b') {\n return value + 1;\n }\n\n return value;\n })\n .of(a, b);\n
The merge callback is given the following arguments:
target: MergeSourceInfo
- The source target information (see below).next: NextCallback
- Callback to invoke for merging nested objects (next depth level).options: Readonly<MergeOptions>
- The merge options to be applied.target: MergeSourceInfo
The source target information object contains the following properties:
result: object
- The resulting object (relative to object depth)key: PropertyKey
- The target property key in source object to.value: any
- Value of the property in source object.source: object
- The source object that holds the property key and value.sourceIndex: number
- Source object's index (relative to object depth).depth: number
- The current recursion depth.next: NextCallback
The callback to perform the merging of nested objects. It accepts the following arguments:
sources: object[]
- The nested objects to be merged.options: Readonly<MergeOptions>
- The merge options to be applied.nextDepth: number
- The next recursion depth number.For additional information about the merge callback, please review the source code of the defaultMergeCallback()
, inside @aedart/support/objects
.
import { mergeKeys } from "@aedart/support/misc";\n\nconst key = mergeKeys(Symbol('my-symbol'), [ 'b', 'c.d' ], 23);\n\nconsole.log(key); // [ Symbol('my-symbol'), 'b', 'c.d', 23 ];\n
import { Mixin } from "@aedart/support/mixins";\n\nexport const RectangleMixin = Mixin((superclass) => class extends superclass {\n length = 0\n width = 0;\n \n area() {\n return this.length * this.width;\n }\n});\n
import { Mixin } from "@aedart/support/mixins";\n\nexport const RectangleMixin = Mixin((superclass) => class extends superclass {\n \n constructor(...args) {\n super(...args); // Invoke parent constructor and pass on arugments!\n \n // Perform your initialisaiton logic...\n }\n \n // ...remaining not shown...\n});\n
Whenever you read metadata, a copy is returned by the getMeta()
method. This means that you can change the data, in your given context, but the original metadata remains the same.
import { meta, getMeta } from '@aedart/support/meta';\n\n@meta('description', { name: 'Search Service', alias: 'Location Sercher' })\nclass Service {}\n\n// Obtain "copy" and change it...\nlet desc = getMeta(Service, 'description');\ndesc.name = 'Country Searcher';\n\n// Original remains unchanged\ngetMeta(Service, 'description').name; // Search Service\n
Caution
Only the meta
decorator is intended to alter existing metadata - even if the value is an object. Please be mindful of this behaviour, whenever you change retrieved metadata using the getMeta()
and getAllMeta()
methods.
populate()
accepts the following arguments:
target: object
source: object
keys: PropertyKey | PropertyKey[] | SourceKeysCallback = '*'
- The keys to select and copy from source
object. If wildcard (*
) given, then all properties from the source
are selected. If a callback is given, then that callback must return key or keys to select from source
.
safe: boolean = true
- When true
, properties must exist in target (must be defined in target), before they are shallow copied.
Caution
The target
object is mutated by this function.
import { populate } from "@aedart/support/objects";\n\nclass Person {\n name = null;\n age = null;\n\n constructor(data) {\n populate(this, data);\n }\n}\n\nconst instance = new Person({ name: 'Janine', age: 36 });\ninstance.name // Janine\ninstance.age // 36\n
By default, all keys (*
) from the source
object are attempted populated into the target
. You can limit what properties can be populated, by specifying what keys are allowed to be populated.
class Person {\n name = null;\n age = null;\n phone = null;\n\n constructor(data) {\n populate(this, data, [ 'name', 'age' ]);\n }\n}\n\nconst instance = new Person({ name: 'Janine', age: 36, phone: '555 555 555' });\ninstance.name // Janine\ninstance.age // 36\ninstance.phone // null\n
If you need a more advanced way to determine what keys to populate, then you can specify a callback as the keys
argument.
populate(target, source, (source, target) => {\n if (Reflect.has(source, 'phone') && Reflect.has(target, 'phone')) {\n return [ 'name', 'age', 'phone' ];\n }\n\n return [ 'name', 'age' ];\n});\n
When the safe
argument is set to true
(default behavior), and a property key does not exist in the target
object, then a TypeError
is thrown.
class Person {\n name = null;\n age = null;\n\n constructor(data) {\n populate(this, data, [ 'name', 'age', 'phone' ]);\n }\n}\n\nconst instance = new Person({\n name: 'Janine',\n age: 36,\n phone: '555 555 555'\n}); // TypeError - phone does not exist in target \n
However, if a requested key does not exist in the source object, then a TypeError
is thrown regardless of the safe
argument value.
class Person {\n name = null;\n age = null;\n\n constructor(data) {\n populate(this, data, [ 'name', 'age', 'phone' ], false);\n }\n}\n\nconst instance = new Person({\n name: 'Janine',\n age: 36\n}); // TypeError - phone does not exist in source \n
If you have discovered a vulnerability, please send an encrypted email to Alin Eugen Deac (aedart@gmail.com). Use the public PGP key listed below for encryption. Your email will be prioritised and addressed as quickly as possible.
In addition, please make sure that the contents of your email contains appropriate information about the vulnerability. E.g.:
-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: Mailvelope v4.4.1\nComment: https://www.mailvelope.com\n\nxsFNBGISJyIBEAC6JEo+xr48WA6fDTVjJKuF3MaT91VBrjynKNxcCBHINLw8\nqIiJYfRFvny0Ffx8xuKlK1rVJ4BpsX4fURb5mFiQtFe6paSBVLUOCDyjlhdz\nsrKwhtz4tDpzc94dCUWkNJgWNa4ah5TuEJZLdXGptYeUtj3/UNqmAxsx6NEj\n/xXLX3eZ+rwn88UVxKoTFge1AwCLmRk0N55s6g1tQJ8MrGZvjjJ0OGCnDKhy\nh3CZI2SqlovL4/MXq0cYexqyUDLcZzEgbAKvNA/vnxc9oIBEIZoRZ1QxqXO6\nZ/Da8ItUJ8Eg8MmWIFmnqs+YfeOcHqxflm288c44BRPN3e3bM9tb8qhi+Xd8\nSE3D0Az1QVq3aoKzvF35bnV0KwB86so8ud4/fVtfBq6kklS6ZSS3wNhCRG3f\nimenk2DvOni9MqriXPOKxVBqa9yC0otua7/IoZGksNOwzjdUwpfY3guTv4iu\nnUcieHBmXtX57N4PHx1DZAWi0Zyh0dop3cnV8my3ZdQ0fR+GSI49z+1gcpSE\nOgANIDb5Ejk3EalmYxv4OKWl9M3sztaP5q7dOAo5vYoTwFv9cnjXaj7+8F7b\nfH0rQi/xJWeHd7BKHzgwz3zP/Wz57wFNvW9Yg0HVjjkBB/fwmqIGtImkVICV\nJfhnWBApezI+m8W4GI777GtG+DhR3nsTJQ43DQARAQABzSJBbGluIEV1Z2Vu\nIERlYWMgPGFlZGFydEBnbWFpbC5jb20+wsF1BBABCAAfBQJiEiciBgsJBwgD\nAgQVCAoCAxYCAQIZAQIbAwIeAQAKCRBcsQfpwXzHeO1kEACHLk3KrWzJ3qqP\nRP4RERnaD3wXy7j2my4zuAEDPq4miEekSfSSgna0yca5L22scDcJYk18wOlX\nf3TprZVkQ0xKKukHMgaJY3ydpug1dEM1Ve9TxzDVUEqiOAxg55P0s5rA8Uec\nexW8lzQcfCnR4ascuPCKoxo/zbvVoIZr3tnKaQirE+DJFcbsJlqNROG/XV+f\nmwjpI2/LIi+qJ/qxmL8iqE4KqUI147Usk1wztQGc6aY0OfK7uxZ+s0YvkbFQ\nlBJoJLG0MVodq9fpiakEzWN0q1OoQEJALrm1OvZD4JtxGSeSVw973y33nBdg\nNDbbwXmeh4mu6ObZgmNImxt/nTLBWsqYTS/3mzyUC/3eimuYWIsSi7A8JJ/s\nAmcG+Wp7yM9rRbiuZG/m0C+HneAICBcJ4QDV84XY4bMgu2zMgyWM5dbd65cR\n7ug+p8z7g7CieH+H1OYXNApn8+7HZjw7CbMsUaAGk4IgndCb1kxxI8+q1Et4\nwaR0mMJ6uOslbP+24uEwrIe2boW4mcssqlNBSfBR3OCWs0VWNvB1tq1NBUZK\nrMuqdPzL/eNtObuNcEndXz18VWWmJBJaGCC7r5ARXB+MzO07B5UTHCu1mXCR\n6t9gqLuSEOwpbK8BJHSuyxYAPPlc7X7BUSlyZVmEqiVgmtHM0F6E4pydR7e4\nuSiBOG7d0c7BTQRiEiciARAAzR2TnBvCNI3/3bl6ZOPtru0Yr++gjIsNYsNc\n61wHDy9mhRENi1AjYngJK4UEmDrm/3rxlIrZrVm2OOXTLFxveGWJozeR1ovk\nog3zojZyYOgr8AabEtRAbsEHwIfjCSXV/z0c4mp05Jtcztll0OM+NE962gRI\ngt7tDzJweWKdsHOF4agQ3+L4g+nD76ZFLnpjoZsfSekdJLtCfl4bYVQgi4/I\nFFpJM3o7CiHeEjm26eaIu5SQ/U2Ciwnepgp7WDq+J9pbQS/5pq8wjSqUO0wW\nzyrD4fEjx2TnBTzV7aea4OumKZB6X+lJwTTpvN7BVF2ODfL2IEohzk14p3kM\nRMSpNgGtFeg7lfFDW9j0zK6vHOkFSDUZ0VbWpj6K6FtsimKx92bSS046Bdu4\n7l8Vx33vGk/QL09YiEJkeqE39DJ28TShyw1mtfUQXAJykaPIO9bpImTgYjwu\numD9y7v3Ubr+9g0Bvsy7byEsppDtcFCuYK1wtd6kvdFZSCEAJIc23JGPwVLJ\nMo7gDgR8W30TBuvhi2hEuwQq1kgE2XgrYMb4BbIo5OTplRqKePvEyq+Xgn1K\nKak4HErs0X6uFcCXynAGINIV2H7pVCtCYC0XtaoG6WvtC6NR1TWGpmhVchY0\n3jCQdBpwvQyuTw4DcSJcbhFmJzo9PdzRN8fu8M4Kh9LapvUAEQEAAcLBXwQY\nAQgACQUCYhInIgIbDAAKCRBcsQfpwXzHeEmKD/4mysrPW/t9BfDE4kWYBCHO\nl54lXV9FfC6xHAR0v9qJTurv87svOZBwWeL8lnyFELB9DCLjgOtNT9ZbUcdm\nHuesBTm9jV83K3uvMBqzPKvxX1IZnxhVsg+KSRocY3PMa2QPCyriKDZ7tsFe\n5yMeXqHOtHuDsVzYJhmCK9oz85tl5xozFVJgmapn007ozAoK6XsQQmjY8U+C\njiL/WHdN9yuFxYnxwCdtJ7Nh72uixs2nRZKjcCzWAFYHo6iJuWbxWG2l4LIq\nRh9x3pLDSCth4xigObAaGiIj4nMWtrX9Re8/lze3BFKfB0IDp0+KUl63afeI\n/xcOoJ5JkhFqe/AJVX203Gr6niqn3ckD08zeB3ZgMgTVTo2uwX03wM9AHz43\n0Sp5n1c321E6mWtrBPTDL8xdBccLETYcmpDKA6jHUepwQTxst3ytbv/G1nZ5\nDj6ADe/CSC95O6CO5BeXkM3gZeMmkuHtxjue1MLLhnGgrp1HMjxp5L92tUMU\n6chem/yKqEE5Ac0TCNMDAs+soQ/ISN++NQtk1f5QEDE2F7Ji8eLAFSclnXbh\nlf1Hu4IaSrvr8q+ctAGIgrh/N4oWDm/jYPT+QPCQEaYKUuGBNHIWWU5Pb+9S\n1IIhuIP3hQAzawPbIeoEgt2lCIii3BjLcZAZ2cVv9KuCG09D7F86j5Dz/R7V\ngA==\n=E13G\n-----END PGP PUBLIC KEY BLOCK-----\n
import { set } from "@aedart/support/objects";\n\nconst target = {};\n\nset(target, 'a.foo', 'bar');\n\nconsole.log(target); // { a: { foo: 'bar } }\n
To obtain metadata, use the getMeta()
method. You can also use getAllMeta()
, if you wish to obtain all available metadata for a target class.
import { meta } from '@aedart/support/meta';\n\n@meta('service_alias', 'locationSearcher')\nclass Service\n{\n @meta('name', 'Name of service') name;\n \n @meta('fetch.desc', 'Fetches resource via a gateway')\n @meta('fetch.dependencies', [ 'my-gateway' ])\n async fetch(gateway)\n {\n // ...implementation not shown...\n }\n}\n
Use getMeta()
or getAllMeta()
to retrieve metadata.
import { getMeta, getAllMeta } from '@aedart/support/meta';\n\nconst service = new Service();\n\nconst desc = getMeta(Service, 'fetch.desc');\nconst dependencies = getMeta(Service, 'fetch.dependencies');\n\n// Or, obtain all metadata\nconst allMeta = getAllMeta(Service);\n
Metadata Availability
Depending on the kind of element that is decorated, metadata might only become available for reading, after a new class instance has been instantiated. This is true for the following elements:
method
getter
setter
field
accessor
Static Elements
',4),g={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static",target:"_blank",rel:"noopener noreferrer"},h=(0,e.Lk)("code",null,"static",-1),f=(0,e.Fv)('The getMeta()
method also offers a defaultValue
argument, which is returned, in case that a metadata value does not exist for a given identifier.
const description = getMeta(Service, 'fetch.desc', 'N/A');\n
If you need to create more advanced metadata, you can specify a callback as the first argument for the meta()
decorator method. When using a callback you gain access to the target
that is being decorated, as well as the decorator context
. The callback MUST return an object that contains a key
and a value
property.
import { meta } from '@aedart/support/meta';\n\nclass Service {\n\n @meta((target, context) => {\n return {\n key: context.name,\n value: '...'\n }\n })\n delegateTo(gateway) {\n // ...not shown...\n }\n}\n
Although the above example is a bit cumbersome to read, it shows a simple way to defined metadata for a method, which utilises the decorator context
. If you wish, you can use this approach to create your own specialised meta decorators. Doing so can also improve the readability of your class. Consider the following example:
import { meta } from '@aedart/support/meta';\n\nfunction delegateMeta() {\n return meta((target, context) => {\n return {\n key: context.name,\n value: '...'\n }\n });\n}\n\nclass Service {\n\n @delegateMeta()\n delegateTo(gateway) {\n // ...not shown...\n }\n}\n
The meta
decorator supports the following elements¹:
class
method
getter
setter
field
accessor
class
method
The following shows how to define target meta for a class and retrieve it.
import { targetMeta, getTargetMeta } from '@aedart/support/meta';\n\n@targetMeta('description', { type: 'Search Service', alias: 'Location Sercher' })\nclass LocationSearcherService {}\n\nconst instance = new LocationSearcherService();\n\n// ...later in your application...\ngetTargetMeta(instance, 'description')?.type; // Search Service\n
The following shows how to define target meta for a class method and retrieve it.
import { targetMeta, getTargetMeta } from '@aedart/support/meta';\n\nclass LocationSearcherService {\n\n @targetMeta('dependencies', [ 'httpClient' ]) \n search(apiClient) {}\n}\n\nconst instance = new LocationSearcherService();\n\n// ...later in your application...\ngetTargetMeta(instance.search, 'dependencies'); // [ 'httpClient' ]\n
Target meta is automatically inherited by subclasses and can also be overwritten, similar to that of the meta()
decorator.
Example: classes
import {targetMeta, getTargetMeta} from '@aedart/support/meta';\n\n@meta('service_alias', 'locationSearcher')\nclass Service {}\n\nclass CitySearcher extends Service {}\n\nconst instance = new CitySearcher();\n\n// ...later in your application...\ngetTargetMeta(instance, 'service_alias'); // locationSearcher\n
Example: methods
import {targetMeta, getTargetMeta} from '@aedart/support/meta';\n\nclass Service {\n\n @targetMeta('dependencies', [ 'countrySearchApiClient' ])\n search(apiClient) {\n // ...not shown...\n }\n}\n\nclass CountrySearcher extends Service {\n // ... not method overwrite here...\n}\n\nclass CitySearcher extends Service {\n\n @targetMeta('dependencies', [ 'citySearchApiClient' ])\n search(apiClient) {\n // ...not shown...\n }\n}\n\nconst instanceA = new Service();\nconst instanceB = new CountrySearcher();\nconst instanceC = new CitySearcher();\n\n// ...later in your application...\ngetTargetMeta(instanceA.search, 'dependencies'); // [ 'countrySearchApiClient' ]\ngetTargetMeta(instanceB.search, 'dependencies'); // [ 'countrySearchApiClient' ]\ngetTargetMeta(instanceC.search, 'dependencies'); // [ 'citySearchApiClient' ]\n
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.
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
import {targetMeta, getTargetMeta} from '@aedart/support/meta';\n\nclass Service {\n\n @targetMeta('dependencies', [ 'xmlClient' ])\n static search(client) {\n // ...not shown...\n }\n}\n\nclass CountrySearcher extends Service {\n // ... not method overwrite here...\n}\n\nclass CitySearcher extends Service {\n \n // Overwite of static method - target meta is lost\n static search(client) {}\n}\n\n// ...later in your application...\ngetTargetMeta(CountrySearcher.search, 'dependencies'); // [ 'xmlClient' ]\ngetTargetMeta(CitySearcher.search, 'dependencies'); // undefined\n
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
import {\n targetMeta,\n getTargetMeta,\n inheritTargetMeta\n} from '@aedart/support/meta';\n\nclass Service {\n\n @targetMeta('dependencies', [ 'xmlClient' ])\n static search(client) {\n // ...not shown...\n }\n}\n\nclass CountrySearcher extends Service {\n // ... not method overwrite here...\n}\n\nclass CitySearcher extends Service {\n \n @inheritTargetMeta()\n static search(client) {}\n}\n\n// ...later in your application...\ngetTargetMeta(CountrySearcher.search, 'dependencies'); // [ 'xmlClient' ]\ngetTargetMeta(CitySearcher.search, 'dependencies'); // [ 'xmlClient' ]\n
Example:
import { meta, getMeta } from '@aedart/support/meta';\n\n@meta('service_alias', 'locationSearcher')\nclass Service {}\n\ngetMeta(Service, 'service_alias'); // locationSearcher\n
Roughly "desugars" to the following:
function meta(key, value) {\n return (target, context) => {\n context.metadata[key] = value;\n }\n}\n\n@meta('service_alias', 'locationSearcher')\nclass Service {}\n\nService[Symbol.metadata].service_alias; // locationSearcher\n
(Above shown example is very simplified. Actual implementation is a bit more complex...)
',5),i=(0,e.Lk)("code",null,"meta",-1),u={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap",target:"_blank",rel:"noopener noreferrer"},d=(0,e.Lk)("code",null,"WeakMap",-1),k={href:"https://github.com/tc39/proposal-decorator-metadata",target:"_blank",rel:"noopener noreferrer"},m=(0,e.Lk)("code",null,"context.metadata",-1),v=(0,e.Lk)("em",null,"or when browsers support it",-1),b={};var h=(0,s(6262).A)(b,[["render",function(a,n){const s=(0,e.g2)("ExternalLinkIcon");return(0,e.uX)(),(0,e.CE)("div",null,[t,(0,e.Lk)("p",null,[(0,e.eW)("In relation to the "),(0,e.Lk)("a",o,[(0,e.eW)("Decorator Metadata proposal"),(0,e.bF)(s)]),(0,e.eW)(", this decorator "),p,(0,e.eW)(" a similar behaviour as the one defined by the proposal. Defining and retrieving metadata relies on a decorator's "),c,(0,e.eW)(" object, and the "),l,(0,e.eW)(" property of a class.")]),r,(0,e.Lk)("p",null,[(0,e.eW)("At present, the internal mechanisms of the "),i,(0,e.eW)(" decorator must rely on a "),(0,e.Lk)("a",u,[d,(0,e.bF)(s)]),(0,e.eW)(" to associate metadata with the intended class. When the "),(0,e.Lk)("a",k,[(0,e.eW)("Decorator Metadata proposal"),(0,e.bF)(s)]),(0,e.eW)(" becomes more mature and transpilers offer the "),m,(0,e.eW)(" object ("),v,(0,e.eW)("), then this decorator will be updated respectfully to use the available metadata object.")])])}]]);const g=JSON.parse('{"path":"/archive/current/packages/support/meta/tc39.html","title":"TC39 Proposal","lang":"en-GB","frontmatter":{"title":"TC39 Proposal","description":"In relation to TC39 Decorator Metadata proposal","sidebarDepth":0},"headers":[],"git":{"updatedTime":1709288721000,"contributors":[{"name":"alin","email":"alin@rspsystems.com","commits":1}]},"filePathRelative":"archive/current/packages/support/meta/tc39.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')}}]); \ No newline at end of file diff --git a/assets/js/toWeakRef.html.4afb7262.js b/assets/js/toWeakRef.html.4afb7262.js new file mode 100644 index 00000000..6817d670 --- /dev/null +++ b/assets/js/toWeakRef.html.4afb7262.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[9912],{9904:function(n,a,s){s.r(a),s.d(a,{comp:function(){return u},data:function(){return k}});var e=s(641);const t={id:"toweakref",tabindex:"-1"},o={class:"header-anchor",href:"#toweakref"},p=(0,e.Lk)("code",null,"toWeakRef",-1),c={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef",target:"_blank",rel:"noopener noreferrer"},i=(0,e.Lk)("code",null,"WeakRef",-1),l=(0,e.Fv)('import { toWeakRef } from "@aedart/support/misc";\n\nconst person = { name: 'Sine' };\n\nconst a = toWeakRef(person); // new WeakRef of "person"\nconst b = toWeakRef(a); // same WeakRef instance as "a"\n\ntoWeakRef(null); // undefined\ntoWeakRef(undefined); // undefined\n
The uniqueId()
is able to return a "unique¹" reference identifier for any given object.
import { uniqueId, hasUniqueId } from "@aedart/support/objects";\n\nconst target = {\n name: 'Ursula'\n};\n\nconsole.log(uniqueId(target)); // 27\n\n// ...later in your application\nconsole.log(hasUniqueId(target)); // true\nconsole.log(uniqueId(target)); // 27\n
The following deprecated types have been removed (deprecated in version v0.7.0
):
ClassContext
MethodContext
GetterContext
SetterContext
FieldContext
AccessorContext
MetadataContext
MemberContext
More information available in the source code and CHANGELOG.md
targetMeta()
and inheritTargetMeta()
)The util functions targetMeta()
and inheritTargetMeta()
now throw a MetaError
instead of previous TypeError
. If you rely on TypeError
as the type of exception being thrown in a try-catch statement, when decorating class members, then you should change it to MetaError
.
Various metadata related type definitions have now been deprecated in favour of TypeScript's own definitions. Mostly, this should not affect the implementation. However, if your decorator(s) depend on the following types (see below), then you are strongly encouraged to use corresponding TypeScript defined types instead.
Deprecated types and interfaces are defined in @aedart/contracts/support/meta
:
ClassContext
MethodContext
GetterContext
SetterContext
FieldContext
AccessorContext
MetadataContext
MemberContext
More information available in the source code and CHANGELOG.md
The @aedart/vuepress-utils
has been upgraded to use vuepress v2.0.0-rc.2
, meaning that you no longer should require to manually define your vuepress
dependency, in your application's packages.json
file.
❌ Previously
{\n "devDependencies": {\n "@aedart/vuepress-utils": "^0.6.1",\n "vuepress": "2.0.0-beta.61",\n "@vuepress/core": "2.0.0-beta.61",\n "@vuepress/utils": "2.0.0-beta.61",\n "@vuepress/client": "2.0.0-beta.61"\n } \n}\n
✔️ Now
{\n "devDependencies": {\n "@aedart/vuepress-utils": "^0.7.0"\n } \n}\n
Webpack Bundle
In addition to the above, the @aedart/vuepress-utils
automatically comes with @vuepress/bundler-webpack
as its peed dependency.
forgetAll()
, hasAll()
and hasAny()
forgetAll()
, hasAll()
and hasAny()
(in @aedart/support/object
submodule) now accept rest parameters instead of an array of paths. If you are using these methods, then you need to upgrade or risk unexpected results.
❌ Previously
import {\n forgetAll,\n hasAll,\n hasAny\n} from "@aedart/support/objects";\n\nhasAny(target, [ 'a', 'b.c.age' ]);\nhasAll(target, [ 'a', 'b.c.age' ]);\nforgetAll(target, [ 'a', 'b.c.age' ]);\n
✔️ Now
hasAny(target, ...[ 'a', 'b.c.age' ]);\nhasAll(target, ...[ 'a', 'b.c.age' ]);\nforgetAll(target, ...[ 'a', 'b.c.age' ]);\n\n// ...Or\nhasAny(target, 'a', 'b.c.age');\nhasAll(target, 'a', 'b.c.age');\nforgetAll(target, 'a', 'b.c.age');\n
import { use } from "@aedart/support/concerns";\n\n@use(\n ApiConnection,\n Serialization,\n Collections\n)\nclass Flight {}\n
All concerns that are used by a parent class are automatically available (inherited), by child classes.
@use(\n ApiConnection,\n Serialization,\n Collections\n)\nclass ApiService {}\n\nclass Flight extends ApiService {} // Uses ApiConnection, Serialization, ...etc\n
WARNING
A concern class may ONLY occur once in a target class' prototype chain. An InjectionError
is thrown, if this is violated!
@use(\n ApiConnection,\n Serialization,\n Collections\n)\nclass ApiService {}\n\n@use(Serialization) // InjectionError\nclass Flight extends ApiService {}\n
import {\n use,\n CONCERNS,\n AbstractConcern\n} from "@aedart/support/concerns";\n\nclass Encryption extends AbstractConcern {\n encrypt(value) { /* ...not shown... */ }\n}\n\n@use(Encryption)\nclass CookieStore {\n constructor() {\n const container = this[CONCERNS];\n const value = container.get(Encryption).encrypt('Lorum lipsum');\n \n // ...remaining not shown...\n }\n}\n
You can achieve the same result by using the getContainer()
utility method.
import { use, getContainer } from "@aedart/support/concerns";\n\n// ...Encryption concern not shown...\n\n@use(Encryption)\nclass CookieStore {\n constructor() {\n const value = getContainer(this)\n .get(Encryption)\n .encrypt('Lorum lipsum');\n \n // ...remaining not shown...\n }\n}\n
There are 3 ways to obtain the concerns container instance:
A) CONCERNS
symbol
Inside your target class, if you know that concerns are used (if target is a "concern owner"), then you can use the CONCERNS
symbol to gain access to the container.
import { CONCERNS } from "@aedart/support/concerns";\n\n// Inside your target class...\nconst container = this[CONCERNS];\n
B) getContainer()
getContainer()
is essentially a just a wrapper for: return this[CONCERNS]
.
import { getContainer } from "@aedart/support/concerns";\n\n// Inside your target class...\nconst container = getContainer(this);\n
C) getConcernsContainer()
The getConcernsContainer()
achieves the same result as the previous shown methods. However, it does perform a check of the provided target instance, which ensures that it is a "concern owner". If the target does not pass this test, then a TypeError
is thrown. This might can be useful in situations when you might now know if the target is a concern owner, e.g. when situated in a child class or outside a target class.
import { getConcernsContainer } from "@aedart/support/concerns";\n\n// Inside your target class...\nconst container = getConcernsContainer(this);\n
To determine if a target uses one or more concerns, use the usesConcerns()
method. It accepts the following arguments:
instance: object|Owner
- The target class instance....concerns: ConcernConstructor[]
- Concern classes to test for.import {\n use,\n AbstractConcern,\n usesConcerns\n} from "@aedart/support/concerns";\n\nclass A extends AbstractConcern {}\nclass B extends AbstractConcern {}\nclass C extends AbstractConcern {}\n\n@use(\n A,\n B\n)\nclass Game {}\n\nconst instance = new Game();\n\nusesConcerns(instance, A); // true\nusesConcerns(instance, B); // true\nusesConcerns(instance, A, B); // true\n\nusesConcerns(instance, C); // false\nusesConcerns(instance, A, C); // false\nusesConcerns(instance, B, C); // false\nusesConcerns(instance, A, B, C); // false\n
The <VersionDisclaimer />
component is a simply "notice" container, which can be used in your layout. Most often, you would use this to display a custom message when outdated / unsupported documentation is being viewed.
<VersionDisclaimer type="warning" label="Note">\n You are viewing documentation for an unsupported version...\n</VersionDisclaimer>\n
type
(optional)The type
property accepts the following values:
info
(default)warning
danger
label
(optional)An optional label that is used as a prefix for the custom disclaim message.
<script setup lang="ts">\nimport ParentLayout from '@vuepress/theme-default/layouts/Layout.vue';\nimport VersionDisclaimer from "@aedart/vuepress-utils/components/VersionDisclaimer.vue";\nimport {usePageData} from "@vuepress/client";\nimport {isViewingNextRef, isViewingOtherRef} from "@aedart/vuepress-utils";\nimport archive from "../my_archive";\n\nconst page = usePageData();\nconst showForNext = isViewingNextRef(page, archive);\nconst showForOther = isViewingOtherRef(page, archive);\n</script>\n\n<template>\n <ParentLayout>\n <template #page-top>\n \n <VersionDisclaimer v-if="showForNext">\n You are viewing documentation for next version...\n </VersionDisclaimer>\n \n <VersionDisclaimer v-if="showForOther" type="danger" label="Oh oh">\n You are viewing old stuff...\n </VersionDisclaimer>\n\n </template>\n </ParentLayout>\n</template>\n
import { defineClientConfig } from '@vuepress/client';\nimport Layout from "./layouts/Layout.vue";\n\nexport default defineClientConfig({\n layouts: {\n Layout\n }\n});\n