2022-09
Refresher: Why metadata?
JSON.stringify
output, or WASM or native platform interop)GET
or POST
, or belonging to a specific url path, or what request body transforms to use)Legacy decorators used to receive the class definition as the first argument. Decorators could use the class as a key in a WeakMap to expose metadata.
const MY_META = new WeakMap();
class Metadata {
public = {};
own;
}
function addMeta(value) {
return function(Class, name, desc) {
let metadata = MY_META.get(Class);
if (!metadata) {
metadata = new Metadata();
MY_META.set(Class, metadata);
}
if (name) {
metadata.public[name] = value;
} else {
metadata.own = value;
}
}
}
@addMeta('class')
class C {
@addMeta('pub') x;
}
const metadata = MY_META.get(C);
metadata.own;
// 'class'
metadata.public.x;
// 'pub'
Decorators can add metadata which is then readable by external code.
const MY_META = Symbol();
function addMeta(value) {
return function(m, context) {
context.setMetadata(MY_META, value);
}
}
@addMeta('class')
class C {
@addMeta('pub') x;
@addMeta('priv') #x;
}
C.prototype[Symbol.metadata][MY_META].own;
// 'class'
C.prototype[Symbol.metadata][MY_META].public.x;
// 'pub'
C.prototype[Symbol.metadata][MY_META].private[0];
// 'priv'
Same general idea as current proposal, but all metadata gets added to a single array.
const MY_META = Symbol();
function addMeta(value) {
return function(m, context) {
context.addMetadata(value);
}
}
@addMeta('class')
class C {
@addMeta('pub') x;
@addMeta('priv') #x;
}
C.prototype[Symbol.metadata][0];
// 'pub'
C.prototype[Symbol.metadata][1];
// 'priv'
C.prototype[Symbol.metadata][2];
// 'class'
Decorators receive an object on `context` that can be used as key for metadata
const METADATA = new WeakMap();
class Metadata {
private = [];
public = {};
own;
}
function addMeta(value) {
return function(m, context) {
let metadata = METADATA.get(context.metadata);
if (!metadata) {
metadata = new Metadata();
METADATA.set(context.metadata, metadata);
}
if (context.private) {
metadata.private.push(value);
} else if (context.kind === 'class') {
metadata.own = value;
} else {
metadata.public[context.name] = value;
}
}
}
@addMeta('class')
class C {
@addMeta('pub') x;
@addMeta('priv') #x;
}
const metadata = METADATA.get(C[Symbol.metadata]);
metadata.own;
// 'class'
metadata.public.x;
// 'pub'
metadata.private[0];
// 'priv'
Decorators receive an object on `context` that can be used as key for metadata
const CONTEXT_TO_METADATA = new WeakMap();
const CLASS_TO_METADATA = new WeakMap();
class Metadata {
// ...
}
function addMeta(value) {
// Similar to previous example...
}
function exposeMeta(C, context) {
const metadata = CONTEXT_TO_METADATA.get(context.metadata);
CLASS_TO_METADATA.set(C, metadata);
}
@exposeMeta
@addMeta('class')
class C {
@addMeta('pub') x;
@addMeta('priv') #x;
}
const metadata = CLASS_TO_METADATA.get(C);
const MY_META = new WeakMap();
class Metadata {
// ...
}
function addMeta(value) {
return function(m, context) {
context.addInitializer(
function() {
let metadata = MY_META.get(this);
if (!metadata) {
metadata = new Metadata();
MY_META.set(this, metadata);
}
// ...
},
'static'
)
}
}
@addMeta('class')
class C {
@addMeta('pub') x;
@addMeta('priv') #x;
}
const metadata = MY_META.get(C);
In the current proposal, users can expose metadata by using class and field decorators that share state.
This is fairly unergonomic and also can be error prone (e.g. if you accidentally reuse a stateful decorator for a different class).
const CLASS_TO_METADATA = new WeakMap();
class Metadata {
// ...
}
function makeMetaDecorators() {
const metadata = new Metadata();
function addMeta(value) {
// Similar to previous example...
}
function exposeMeta(C, context) {
CLASS_TO_METADATA.set(C, metadata);
}
return { addMeta, exposeMeta };
}
const { addMeta, exposeMeta } = makeMetaDecorators();
@exposeMeta
@addMeta('class')
class C {
@addMeta('pub') x;
@addMeta('priv') #x;
}
const metadata = CLASS_TO_METADATA.get(C);