-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Metadata): Add the metadata package
- Loading branch information
1 parent
4bf86e5
commit 6de6b5d
Showing
10 changed files
with
223 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { createMetadataDecorator } from './createMetadataDecorator.js' | ||
import { type } from './type.js' | ||
|
||
const meta = createMetadataDecorator(Symbol('meta')) | ||
|
||
describe('createMetadata', () => { | ||
class TestClassZero { } | ||
@meta('Test #1') class TestClassOne { } | ||
class TestClassTwo extends TestClassOne { } | ||
@meta('Test #3') class TestClassThree extends TestClassTwo { } | ||
|
||
describe('on class', () => { | ||
it('should return undefined for a class without the metadata', () => { | ||
expect(meta.get(TestClassZero)).toBe(undefined) | ||
}) | ||
|
||
it('should attach the metadata onto a class', () => { | ||
expect(meta.get(TestClassOne)).toBe('Test #1') | ||
}) | ||
|
||
it('should inherit the metadata from a parent class', () => { | ||
expect(meta.get(TestClassTwo)).toBe('Test #1') | ||
}) | ||
|
||
it('should override the metadata from a parent class', () => { | ||
expect(meta.get(TestClassThree)).toBe('Test #3') | ||
}) | ||
}) | ||
|
||
describe('on properties', () => { | ||
class TestPropertyOne { | ||
@meta('Some property') prop?: string | ||
} | ||
|
||
class TestPropertyTwo extends TestPropertyOne { | ||
override prop = '' | ||
} | ||
|
||
class TestPropertyThree extends TestPropertyTwo { | ||
@meta('Overridden property') override prop = '' | ||
nested = new TestPropertyOne() | ||
} | ||
|
||
it('should attach the metadata onto a property', () => { | ||
expect(meta.get(TestPropertyOne, 'prop')).toBe('Some property') | ||
}) | ||
|
||
it('should inherit the metadata from a parent property', () => { | ||
expect(meta.get(TestPropertyTwo, 'prop')).toBe('Some property') | ||
}) | ||
|
||
it('should override the metadata from a parent property', () => { | ||
expect(meta.get(TestPropertyThree, 'prop')).toBe('Overridden property') | ||
}) | ||
}) | ||
|
||
describe('by key path', () => { | ||
class TestKeyPathOne { | ||
@meta('Some property') property?: string | ||
} | ||
|
||
class TestKeyPathTwo { | ||
@type(TestKeyPathOne) | ||
@meta('Property One') one?: TestKeyPathOne | ||
} | ||
|
||
it('should return the metadata by key path', () => { | ||
expect(meta.getByKeyPath(TestKeyPathTwo, 'one.property')).toBe('Some property') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Creates a metadata decorator with the given key supporting both class and property metadata. | ||
* It also provides a getter for retrieving metadata by key and key-path (e.g. 'property.subProperty'). | ||
* The key-path getter needs the property to be decorated with `@type(SubPropertyType)` | ||
* for the generated decorator to be able to resolve the metadata. | ||
*/ | ||
export function createMetadataDecorator(key: symbol) { | ||
function metadata(value: unknown) { | ||
return (target: any, propertyKey?: string) => { | ||
Reflect.defineMetadata(key, value, target, propertyKey!) | ||
} | ||
} | ||
|
||
metadata.get = function (constructor: Constructor<any>, propertyKey?: string) { | ||
return propertyKey === undefined | ||
? Reflect.getMetadata(key, constructor) | ||
: Reflect.getMetadata(key, constructor.prototype, propertyKey) | ||
} | ||
|
||
metadata.getByKeyPath = function <T>(constructor: Constructor<T>, keyPath: KeyPathOf<T>) { | ||
const keys = keyPath.split('.') | ||
const key = keys.pop() as string | ||
const parent = keys.reduce((previousType, key) => type.get(previousType, key), constructor) | ||
if (!parent) { | ||
throw new Error(`Could not resolve type for key path "${keyPath}". Ensure nested properties are decorated with @type(SubPropertyType)`) | ||
} | ||
return metadata.get(parent, key) | ||
} | ||
|
||
return metadata | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { createMetadataDecorator } from './createMetadataDecorator.js' | ||
|
||
export const description = createMetadataDecorator(Symbol('description')) | ||
globalThis.description = description | ||
|
||
declare global { | ||
// eslint-disable-next-line no-var | ||
var description: typeof import('./description.js').description | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import 'reflect-metadata' | ||
import '@a11d/constructor' | ||
import '@a11d/key-path' | ||
export * from './createMetadataDecorator.js' | ||
export * from './type.js' | ||
import './type.js' | ||
export * from './label.js' | ||
import './label.js' | ||
export * from './description.js' | ||
import './description.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { createMetadataDecorator } from './createMetadataDecorator.js' | ||
|
||
export const label = createMetadataDecorator(Symbol('label')) | ||
globalThis.label = label | ||
|
||
declare global { | ||
// eslint-disable-next-line no-var | ||
var label: typeof import('./label.js').label | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "@a11d/metadata", | ||
"version": "0.1.0", | ||
"description": "A set of behavior-less metadata decorators.", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/a11delavar/lit-application.git" | ||
}, | ||
"keywords": [ | ||
"metadata", | ||
"decorator", | ||
"attribute", | ||
"reflect", | ||
"reflect-metadata" | ||
], | ||
"author": "a11delavar", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/a11delavar/lit-application/issues" | ||
}, | ||
"homepage": "https://github.com/a11delavar/lit-application#readme", | ||
"type": "module", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "tsc" | ||
}, | ||
"dependencies": { | ||
"tslib": "x", | ||
"reflect-metadata": "0.x", | ||
"@a11d/key-path": "1.x", | ||
"@a11d/constructor": "x" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "../../tsconfig.base.json", | ||
"exclude": [ | ||
"./dist", | ||
"*.test.ts" | ||
], | ||
"compilerOptions": { | ||
"baseUrl": ".", | ||
"outDir": "./dist" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
const key = 'design:type' | ||
|
||
/** | ||
* A general-purpose metadata decorator to store the type of a property in runtime. | ||
*/ | ||
export function type<Target, TKey extends keyof Target>(type: Constructor<Target[TKey]>) { | ||
return (target: Target, propertyKey?: TKey) => { | ||
Reflect.defineMetadata(key, type, target as any, propertyKey as any) | ||
} | ||
} | ||
|
||
type.get = function (constructor: Constructor<any>, propertyKey: string) { | ||
return Reflect.getMetadata(key, constructor.prototype, propertyKey) | ||
} | ||
|
||
globalThis.type = type | ||
|
||
declare global { | ||
// eslint-disable-next-line no-var | ||
var type: typeof import('./type.js').type & { | ||
get: typeof import('./type.js').type.get | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters