Skip to content

Commit

Permalink
Updates proposal to the latest design
Browse files Browse the repository at this point in the history
  • Loading branch information
pzuraq committed Mar 7, 2023
1 parent 0d14dc3 commit 8380c3f
Showing 1 changed file with 32 additions and 103 deletions.
135 changes: 32 additions & 103 deletions README.md
Expand Up @@ -31,9 +31,10 @@ recent version, however, as decorators only have access to the value they are
_directly_ decorating (e.g. method decorators have access to the method, field
decorators have access to the field, etc).

This proposal extends decorators by providing a value to use as a key to
associate metadata with. This key is then accessible via the
`Symbol.metadataKey` property on the class definition.
This proposal extends decorators by providing a metadata _object_, which can be
used either to directly store metadata, or as a WeakMap key. This object is
provided via the decorator's context argument, and is then accessible via the
`Symbol.metadataKey` property on the class definition after decoration.

## Detailed Design

Expand All @@ -54,134 +55,62 @@ type Decorator = (value: Input, context: {
isPrivate?: boolean;
isStatic?: boolean;
addInitializer?(initializer: () => void): void;
+ metadataKey?: MetadataKey;
+ class?: {
+ metadataKey: MetadataKey;
+ name: string;
+ }
+ metadata?: Record<string | number | symbol, unknown>;
}) => Output | void;
```

Two new values are introduced, `metadataKey` and `class`.
The new `metadata` property is a plain JavaScript object. The same object is
passed to every decorator applied to a class or any of its elements. After the
class has been fully defined, it is assigned to the `Symbol.prototype` property
of the class.

### `metadataKey`

`metadataKey` is present for any _tangible_ decoratable value, specifically:

- Classes
- Class methods
- Class accessors and auto-accessors

It is not present for class fields because they have no tangible value (e.g.
there is nothing to associate the metadata with, directly or indirectly).
`metadataKey` is then set on the decorated value once decoration has completed:
An example usage might look like:

```js
const METADATA = new WeakMap();

function meta(value) {
function meta(key, value) {
return (_, context) => {
METADATA.set(context.metadataKey, value);
context.metadata[key] = value;
};
}

@meta('a')
@meta('a' 'x')
class C {
@meta('b')
@meta('b', 'y')
m() {}
}

METADATA.get(C[Symbol.metadata]); // 'a'
METADATA.get(C.m[Symbol.metadata]); // 'b'
C[Symbol.metadata].a; // 'x'
C[Symbol.metadata].b; // 'y'
```

This allows metadata to be associated directly with the decorated value.
### Inheritance

### `class`

The `class` object is available for all _class element_ decorators, including
fields. The `class` object contains two values:

1. The `metadataKey` for the class itself
2. The name of the class

This allows decorators for class elements to associate metadata with the class.
For method decorators, this can simplify certain flows. For class fields, since
they have no tangible value to associate metadata with, the class metadata key
is the only way to store their metadata.
If the decorated class has a parent class, then the prototype of the `metadata`
object is set to the metadata object of the superclass. This allows metadata to
be inherited in a natural way, taking advantage of shadowing by default,
mirroring class inheritance. For example:

```js
const METADATA = new WeakMap();
const CLASS = Symbol();

function meta(value) {
function meta(key, value) {
return (_, context) => {
const metadataKey = context.class?.metadataKey ?? context.metadataKey;
const metadataName = context.kind === 'class' ? CLASS : context.name;

let meta = METADATA.get(metadataKey);

if (meta === undefined) {
meta = new Map();
METADATA.set(metadataKey, meta);
}

meta.set(metadataName, value);
context.metadata[key] = value;
};
}

@meta('a')
@meta('a' 'x')
class C {
@meta('b')
foo;

@meta('c')
get bar() {}

@meta('d')
baz() {}
}

// Accessing the metadata
const meta = METADATA.get(C[Symbol.metadataKey]);

meta.get(CLASS); // 'a';
meta.get('foo'); // 'b';
meta.get('bar'); // 'c';
meta.get('baz'); // 'd';
```
### `parent`
Metadata keys also have a `parent` property. This is set to the value of
`Symbol.metadataKey` on the prototype of the value being decorated.
```js
const METADATA = new WeakMap();

function meta(value) {
return (_, context) => {
const classMetaKey = context.class.metadataKey;
const existingValue = METADATA.get(classMetaKey.parent) ?? 0;

METADATA.set(classMetaKey, existingValue + value);
};
@meta('b', 'y')
m() {}
}

class C {
@meta(1)
foo;
}
C[Symbol.metadata].a; // 'x'
C[Symbol.metadata].b; // 'y'

class D extends C {
@meta(2)
foo;
@meta('b', 'z')
m() {}
}

// Accessing the metadata
METADATA.get(C[Symbol.metadataKey]); // 3
D[Symbol.metadata].a; // 'x'
D[Symbol.metadata].b; // 'z'
```
## Examples
Todo

0 comments on commit 8380c3f

Please sign in to comment.