diff --git a/404.html b/404.html new file mode 100644 index 00000000..c9f22d0b --- /dev/null +++ b/404.html @@ -0,0 +1,34 @@ + + +
+ + + + + +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 production. 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
HighlightsAdaptation 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!
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
+
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.
The meta
decorator supports the following elements¹:
class
method
getter
setter
field
accessor
¹: An element is determined by the decorator's context.kind
property.
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, getMeta, getAllMeta} 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...
+ }
+}
+
+// Later in your application...
+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 - method has no description');
+
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...
+ }
+}
+
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.
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.
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
.
Supported Elements
Unlike the meta()
decorator, targetMeta()
only supports the following elements:
class
method
Example: class instance
import {targetMeta, getTargetMeta} from '@aedart/support/meta';
+
+@targetMeta('description', { type: 'Search Service', alias: 'Location Sercher' })
+class LocationSearcherService {}
+
+const instance = new LocationSearcherService();
+
+// ...later in your application...
+getTargetMeta(instance, 'description')?.type; // Search Service
+
Example: method reference
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' ]
+
@aedart/support/misc
offers miscellaneous utility methods.
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
+
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.
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
+
@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
+
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...
+});
+
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,
+) {
+ // ...
+}
+
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
+
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
+
For more information and examples, please read Mozilla's documentation about "Mix-ins", and Justin Fagnani's blog posts:
The @aedart/support/objects
submodule offers object related utilities.
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
+
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
+
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!
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).
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
+
+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
+gA==
+=E13G
+-----END PGP PUBLIC KEY BLOCK-----
+
Please read the Support Policy for additional information about what versions are supported.
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...
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
get
See also set()
.
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
has
See also isset()
.
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
hasAll
Determine if all paths are properties of given object.
See also isset()
.
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
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 an object has a unique id.
See uniqueId
for additional details.
import {hasUniqueId} from "@aedart/support/objects";\n\nconst target = {\n name: 'Ursula'\n};\n\nconsole.log(hasUniqueId(target)); // false\n
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
.
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
set
import {set} from "@aedart/support/objects";\n\nconst target = {};\n\nset(target, 'a.foo', 'bar');\n\nconsole.log(target); // { a: { foo: 'bar } }\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 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.
npm install --save-dev @aedart/vuepress-utils\n
yarn add --dev @aedart/vuepress-utils\n
pnpm add --save-dev @aedart/vuepress-utils\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),d={href:"https://github.com/zloirock/core-js#function-iscallable-isconstructor-",target:"_blank",rel:"noopener noreferrer"},m=(0,t._)("em",null,"License MIT",-1),f={};var h=(0,a(3744).Z)(f,[["render",function(n,s){const a=(0,t.up)("Badge"),f=(0,t.up)("router-link"),h=(0,t.up)("ExternalLinkIcon");return(0,t.wg)(),(0,t.iD)("div",null,[(0,t._)("h1",e,[(0,t._)("a",o,[(0,t._)("span",null,[(0,t.Uk)("Reflections "),(0,t.Wm)(a,{type:"tip",text:"Available since v0.7",vertical:"middle"})])])]),c,(0,t._)("nav",i,[(0,t._)("ul",null,[(0,t._)("li",null,[(0,t.Wm)(f,{to:"#isconstructor"},{default:(0,t.w5)((()=>[(0,t.Uk)("isConstructor")])),_:1})])])]),p,(0,t._)("p",null,[(0,t.Uk)("Based on the "),(0,t._)("a",l,[(0,t.Uk)("TC39 "),u,(0,t.Wm)(h)]),(0,t.Uk)(" proposal, the "),r,(0,t.Uk)(" can determine if given argument is a constructor.")]),k,(0,t._)("p",null,[(0,t.Uk)("The source code of the above shown methods is heavily inspired by Denis Pushkarev's Core-js implementation of the "),(0,t._)("a",d,[(0,t.Uk)("Function.isCallable / Function.isConstructor"),(0,t.Wm)(h)]),(0,t.Uk)(" proposal ("),m,(0,t.Uk)(").")])])}]])}}]); \ No newline at end of file diff --git a/assets/js/v-2d9bb678.e4596229.js b/assets/js/v-2d9bb678.e4596229.js new file mode 100644 index 00000000..ae45c614 --- /dev/null +++ b/assets/js/v-2d9bb678.e4596229.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[755],{9553:function(n,a,s){s.r(a),s.d(a,{data:function(){return t}});const t=JSON.parse('{"key":"v-2d9bb678","path":"/archive/current/packages/vuepress-utils/components/version-disclaimer.html","title":"Version Disclaimer","lang":"en-GB","frontmatter":{"title":"Version Disclaimer","description":"A simple notice container","sidebarDepth":0},"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":[]}],"git":{"updatedTime":1679769520000,"contributors":[{"name":"Alin Eugen Deac","email":"aedart@gmail.com","commits":1}]},"filePathRelative":"archive/current/packages/vuepress-utils/components/version-disclaimer.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')},8498:function(n,a,s){s.r(a),s.d(a,{default:function(){return h}});var t=s(6252);const e=(0,t.uE)('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
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(3744).Z)(c,[["render",function(e,n){return(0,t.wg)(),(0,t.iD)("div",null,a)}]])}}]); \ No newline at end of file diff --git a/assets/js/v-4ada6c94.73df1f35.js b/assets/js/v-4ada6c94.73df1f35.js new file mode 100644 index 00000000..ca19f7f4 --- /dev/null +++ b/assets/js/v-4ada6c94.73df1f35.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[425],{4464:function(e,a,t){t.r(a),t.d(a,{data:function(){return n}});const n=JSON.parse('{"key":"v-4ada6c94","path":"/archive/next/","title":"Not Available","lang":"en-GB","frontmatter":{"title":"Not Available","description":"Next Ion Version","sidebarDepth":0},"headers":[],"git":{"updatedTime":1679232326000,"contributors":[{"name":"Alin Eugen Deac","email":"aedart@gmail.com","commits":2}]},"filePathRelative":"archive/next/README.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')},8181:function(e,a,t){t.r(a),t.d(a,{default:function(){return r}});var n=t(6252);const i=[(0,n._)("h1",{id:"not-available",tabindex:"-1"},[(0,n._)("a",{class:"header-anchor",href:"#not-available"},[(0,n._)("span",null,"Not Available")])],-1),(0,n._)("p",null,[(0,n._)("em",null,"The next version of Ion has yet to be designed and implemented. Come back at a later time to review the documentation...")],-1)],o={};var r=(0,t(3744).Z)(o,[["render",function(e,a){return(0,n.wg)(),(0,n.iD)("div",null,i)}]])}}]); \ No newline at end of file diff --git a/assets/js/v-5d96db26.5a6fea9d.js b/assets/js/v-5d96db26.5a6fea9d.js new file mode 100644 index 00000000..b66eebbc --- /dev/null +++ b/assets/js/v-5d96db26.5a6fea9d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[863],{6432:function(t,e,a){a.r(e),a.d(e,{data:function(){return n}});const n=JSON.parse('{"key":"v-5d96db26","path":"/archive/current/packages/support/","title":"Introduction","lang":"en-GB","frontmatter":{"title":"Introduction","description":"Ion Support package","sidebarDepth":0},"headers":[],"git":{"updatedTime":1680807324000,"contributors":[{"name":"Alin Eugen Deac","email":"aedart@gmail.com","commits":1}]},"filePathRelative":"archive/current/packages/support/README.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')},1568:function(t,e,a){a.r(e),a.d(e,{default:function(){return d}});var n=a(6252);const r={id:"introduction",tabindex:"-1"},i={class:"header-anchor",href:"#introduction"},o=(0,n._)("p",null,"The support package offers various utilities.",-1),c={};var d=(0,a(3744).Z)(c,[["render",function(t,e){const a=(0,n.up)("Badge");return(0,n.wg)(),(0,n.iD)("div",null,[(0,n._)("h1",r,[(0,n._)("a",i,[(0,n._)("span",null,[(0,n.Uk)("Introduction "),(0,n.Wm)(a,{type:"tip",text:"Available since v0.3",vertical:"middle"}),(0,n.Wm)(a,{type:"success",text:"Browser",vertical:"middle"})])])]),o])}]])}}]); \ No newline at end of file diff --git a/assets/js/v-5f62758e.dc3b8de1.js b/assets/js/v-5f62758e.dc3b8de1.js new file mode 100644 index 00000000..6a61b937 --- /dev/null +++ b/assets/js/v-5f62758e.dc3b8de1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_aedart_ion_monorepo=self.webpackChunk_aedart_ion_monorepo||[]).push([[161],{8047:function(n,a,s){s.r(a),s.d(a,{data:function(){return e}});const e=JSON.parse('{"key":"v-5f62758e","path":"/archive/current/packages/support/meta.html","title":"Meta","lang":"en-GB","frontmatter":{"title":"Meta","description":"Add arbitrary metadata on classes, methods and properties.","sidebarDepth":0},"headers":[{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Supported Elements","slug":"supported-elements","link":"#supported-elements","children":[]},{"level":2,"title":"Defining and Retrieving Metadata","slug":"defining-and-retrieving-metadata","link":"#defining-and-retrieving-metadata","children":[{"level":3,"title":"Default Value","slug":"default-value","link":"#default-value","children":[]},{"level":3,"title":"Callback","slug":"callback","link":"#callback","children":[]}]},{"level":2,"title":"Inheritance","slug":"inheritance","link":"#inheritance","children":[{"level":3,"title":"Overwrites","slug":"overwrites","link":"#overwrites","children":[]}]},{"level":2,"title":"Changes outside the decorator","slug":"changes-outside-the-decorator","link":"#changes-outside-the-decorator","children":[]},{"level":2,"title":"TC39 Decorator Metadata","slug":"tc39-decorator-metadata","link":"#tc39-decorator-metadata","children":[]},{"level":2,"title":"Target Meta","slug":"target-meta","link":"#target-meta","children":[{"level":3,"title":"Inheritance","slug":"inheritance-1","link":"#inheritance-1","children":[]}]}],"git":{"updatedTime":1706882801000,"contributors":[{"name":"Alin Eugen Deac","email":"aedart@gmail.com","commits":6},{"name":"alin","email":"alin@rspsystems.com","commits":2}]},"filePathRelative":"archive/current/packages/support/meta.md","lastUpdatedDateFormat":"yyyy-MM-dd HH:mm:ss ZZZZ","lastUpdatedDateOptions":{}}')},7864:function(n,a,s){s.r(a),s.d(a,{default:function(){return Y}});var e=s(6252);const t={id:"meta",tabindex:"-1"},p={class:"header-anchor",href:"#meta"},c=(0,e.uE)('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
The meta
decorator supports the following elements¹:
class
method
getter
setter
field
accessor
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, getMeta, getAllMeta} 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\n// Later in your application...\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),x={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static",target:"_blank",rel:"noopener noreferrer"},U=(0,e._)("code",null,"static",-1),C=(0,e.uE)('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 - method has no description');\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
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
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.
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),q=(0,e._)("code",null,"meta",-1),I={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap",target:"_blank",rel:"noopener noreferrer"},O=(0,e._)("code",null,"WeakMap",-1),L={href:"https://github.com/tc39/proposal-decorator-metadata",target:"_blank",rel:"noopener noreferrer"},R=(0,e._)("code",null,"context.metadata",-1),P=(0,e._)("em",null,"or when browsers support it",-1),z={id:"target-meta",tabindex:"-1"},B={class:"header-anchor",href:"#target-meta"},G=(0,e._)("p",null,[(0,e.Uk)("The "),(0,e._)("code",null,"targetMeta()"),(0,e.Uk)(" 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.")],-1),J=(0,e._)("code",null,"targetMeta()",-1),Z=(0,e._)("code",null,"meta()",-1),V={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap",target:"_blank",rel:"noopener noreferrer"},F=(0,e._)("code",null,"WeakMap",-1),H=(0,e.uE)('Supported Elements
Unlike the meta()
decorator, targetMeta()
only supports the following elements:
class
method
Example: class instance
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
Example: method reference
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
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
empty
Determine if value is empty.
See also isset()
.
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 {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
isPrimitive
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.
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
isset
Determine if value is different from undefined
and null
.
See also empty()
.
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
The mergeKeys()
method is able to merge two or more keys into a single key (see isKey()
).
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 { 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
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 { 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
When time permits it, I will review your issue and take action upon it.
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
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
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
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 \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
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
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
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\n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\ngA==\n=E13G\n-----END PGP PUBLIC KEY BLOCK-----\n
npm install --save-peer @aedart/support\n
yarn add --peer @aedart/support\n
pnpm add --save-peer @aedart/support\n
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
npm install --save-peer @aedart/contracts\n
yarn add --peer @aedart/contracts\n
pnpm add --save-peer @aedart/contracts\n