@decaf-ts/decoration 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +385 -0
- package/dist/decoration.cjs +649 -0
- package/dist/decoration.esm.cjs +633 -0
- package/lib/constants.cjs +64 -0
- package/lib/constants.d.ts +58 -0
- package/lib/decoration/Decoration.cjs +270 -0
- package/lib/decoration/Decoration.d.ts +196 -0
- package/lib/decoration/index.cjs +19 -0
- package/lib/decoration/index.d.ts +2 -0
- package/lib/decoration/types.cjs +3 -0
- package/lib/decoration/types.d.ts +95 -0
- package/lib/decorators.cjs +97 -0
- package/lib/decorators.d.ts +57 -0
- package/lib/esm/constants.d.ts +58 -0
- package/lib/esm/constants.js +61 -0
- package/lib/esm/decoration/Decoration.d.ts +196 -0
- package/lib/esm/decoration/Decoration.js +266 -0
- package/lib/esm/decoration/index.d.ts +2 -0
- package/lib/esm/decoration/index.js +3 -0
- package/lib/esm/decoration/types.d.ts +95 -0
- package/lib/esm/decoration/types.js +2 -0
- package/lib/esm/decorators.d.ts +57 -0
- package/lib/esm/decorators.js +90 -0
- package/lib/esm/index.d.ts +21 -0
- package/lib/esm/index.js +22 -0
- package/lib/esm/metadata/Metadata.d.ts +99 -0
- package/lib/esm/metadata/Metadata.js +199 -0
- package/lib/esm/metadata/index.d.ts +2 -0
- package/lib/esm/metadata/index.js +3 -0
- package/lib/esm/metadata/types.d.ts +16 -0
- package/lib/esm/metadata/types.js +2 -0
- package/lib/index.cjs +39 -0
- package/lib/index.d.ts +21 -0
- package/lib/metadata/Metadata.cjs +203 -0
- package/lib/metadata/Metadata.d.ts +99 -0
- package/lib/metadata/index.cjs +19 -0
- package/lib/metadata/index.d.ts +2 -0
- package/lib/metadata/types.cjs +4 -0
- package/lib/metadata/types.d.ts +16 -0
- package/package.json +114 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tiago Venceslau
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
[](https://decaf-ts.github.io/ts-workspace/)
|
|
2
|
+
## @decaf-ts/decoration
|
|
3
|
+
|
|
4
|
+
The decoration module provides a small, composable system for building and applying TypeScript decorators with flavour-aware resolution and a centralized runtime Metadata store. It lets you define base decorators, provide framework-specific overrides and extensions ("flavours"), and record/read rich metadata for classes and their members at runtime.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
[](https://github.com/decaf-ts/ts-workspace/actions/workflows/nodejs-build-prod.yaml)
|
|
12
|
+
[](https://github.com/decaf-ts/ts-workspace/actions/workflows/codeql-analysis.yml)[](https://github.com/decaf-ts/ts-workspace/actions/workflows/snyk-analysis.yaml)
|
|
13
|
+
[](https://github.com/decaf-ts/ts-workspace/actions/workflows/pages.yaml)
|
|
14
|
+
[](https://github.com/decaf-ts/ts-workspace/actions/workflows/release-on-tag.yaml)
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+

|
|
18
|
+

|
|
19
|
+

|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+
|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
Documentation available [here](https://decaf-ts.github.io/ts-workspace/)
|
|
29
|
+
|
|
30
|
+
# Description
|
|
31
|
+
|
|
32
|
+
@decaf-ts/decoration provides two complementary capabilities:
|
|
33
|
+
|
|
34
|
+
- A small, builder-style API (Decoration) to define and apply decorators that can vary by "flavour" (for example, different frameworks or environments) while keeping a stable key-based API.
|
|
35
|
+
- A centralized runtime Metadata store (Metadata) for reading and writing structured information about classes and their members, using reflect-metadata for design-time type hints.
|
|
36
|
+
|
|
37
|
+
This module aims to standardize how decorators are composed, discovered, and executed across contexts, and how metadata is stored and queried during runtime.
|
|
38
|
+
|
|
39
|
+
Main building blocks
|
|
40
|
+
|
|
41
|
+
- Decoration (builder and registry)
|
|
42
|
+
- You create a decoration pipeline for a key (for("component"), for example).
|
|
43
|
+
- For the default flavour ("decaf"), you define the base decorators with define(...).
|
|
44
|
+
- For other flavours (e.g., "vue", "nest"), you can either override the base decorators (define for that flavour) or extend them with extras (extend(...)).
|
|
45
|
+
- At runtime, a global flavour resolver (setFlavourResolver) decides which flavour to apply to a given target. If that flavour has overrides, they are used; otherwise, the default base decorators are applied. Any flavour-specific extras and default extras are appended.
|
|
46
|
+
- The result of apply() is a decorator function whose name encodes the flavour and key to aid debugging.
|
|
47
|
+
|
|
48
|
+
- Decorator utilities
|
|
49
|
+
- metadata(key, value): writes arbitrary metadata on a class or member.
|
|
50
|
+
- prop(): captures the Reflect design:type for a property and records it in Metadata under properties.<prop>.
|
|
51
|
+
- apply(...decorators): composes multiple decorators of different kinds and applies them in sequence.
|
|
52
|
+
- propMetadata(key, value): convenience factory that performs both metadata(key, value) and prop().
|
|
53
|
+
- description(text): stores a human-friendly description for a class or property under description.class or description.<prop>.
|
|
54
|
+
|
|
55
|
+
- Metadata store
|
|
56
|
+
- Static convenience API backed by a singleton instance. It maps each class constructor to a structured metadata object.
|
|
57
|
+
- Keys are nested (e.g., "properties.name", "description.class") and navigated using a splitter (default ".").
|
|
58
|
+
- When mirroring is enabled (default), the internal metadata object is also defined on the constructor under a stable, non-enumerable key (DecorationKeys.REFLECT) for quick inspection.
|
|
59
|
+
- Helper methods:
|
|
60
|
+
- properties(ctor): list known property names that have recorded type info.
|
|
61
|
+
- description(ctor, prop?): read description for class or a property.
|
|
62
|
+
- type(ctor, prop): read the recorded design type for a property.
|
|
63
|
+
- get/set(ctor, key): low-level access to arbitrary paths.
|
|
64
|
+
|
|
65
|
+
Constants and types
|
|
66
|
+
|
|
67
|
+
- DefaultFlavour: the default flavour identifier ("decaf").
|
|
68
|
+
- ObjectKeySplitter: delimiter for nested metadata keys (".").
|
|
69
|
+
- DecorationKeys: well-known metadata keys (REFLECT, PROPERTIES, CLASS, DESCRIPTION, design:type, etc.).
|
|
70
|
+
- DefaultMetadata: the default metadata shape used as a base.
|
|
71
|
+
- BasicMetadata / Constructor: utility types that describe metadata structure and a class constructor signature.
|
|
72
|
+
|
|
73
|
+
Design highlights
|
|
74
|
+
|
|
75
|
+
- Composable and flavour-aware: The Decoration builder allows different environments or frameworks to contribute distinct decorator behavior under the same semantic key. This is useful when building adapters (e.g., Angular vs React components) without changing user code imports.
|
|
76
|
+
- Predictable application order: Default decorators are applied first (or a flavour-specific override is used), followed by extras. Extras can come from the default flavour and/or the resolved flavour.
|
|
77
|
+
- Introspectable metadata: The Metadata store makes it straightforward to record and query runtime facts about models, properties, and behavior. This is especially helpful for ORMs, serializers, validators, and UI bindings.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# How to Use
|
|
81
|
+
|
|
82
|
+
This guide shows practical examples for all main elements of @decaf-ts/decoration. Each example includes a short description and a TypeScript snippet.
|
|
83
|
+
|
|
84
|
+
Prerequisites
|
|
85
|
+
|
|
86
|
+
- Enable experimental decorators and emit decorator metadata in tsconfig.json:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"compilerOptions": {
|
|
91
|
+
"experimentalDecorators": true,
|
|
92
|
+
"emitDecoratorMetadata": true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- Import reflect-metadata once in your test/app entry:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import "reflect-metadata";
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Decoration class
|
|
104
|
+
|
|
105
|
+
1) Define base decorators for the default flavour ("decaf")
|
|
106
|
+
|
|
107
|
+
Description: Create a decoration pipeline for key "component" that applies two decorators to any class.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { Decoration } from "@decaf-ts/decoration";
|
|
111
|
+
|
|
112
|
+
// A simple class decorator factory (just logs)
|
|
113
|
+
const logFactory = (tag: string): ClassDecorator => (target) => {
|
|
114
|
+
console.log(`[${tag}]`, (target as any).name);
|
|
115
|
+
};
|
|
116
|
+
const mark: ClassDecorator = (t) => {
|
|
117
|
+
(t as any).__mark = true;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Register base decorators for the default flavour
|
|
121
|
+
const component = Decoration.for("component")
|
|
122
|
+
.define({ decorator: logFactory, args: ["base"] }, mark)
|
|
123
|
+
.apply();
|
|
124
|
+
|
|
125
|
+
// Use it
|
|
126
|
+
@component
|
|
127
|
+
class MyComponent {}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
2) Extend base decorators with flavour-specific extras
|
|
131
|
+
|
|
132
|
+
Description: Provide extra behavior when the runtime flavour is resolved to "web" while keeping default base decorators.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { Decoration } from "@decaf-ts/decoration";
|
|
136
|
+
|
|
137
|
+
// Default base
|
|
138
|
+
Decoration.for("component")
|
|
139
|
+
.define(((t: any) => t) as ClassDecorator)
|
|
140
|
+
.apply();
|
|
141
|
+
|
|
142
|
+
// Flavour-specific extras
|
|
143
|
+
Decoration.flavouredAs("web")
|
|
144
|
+
.for("component")
|
|
145
|
+
.extend({
|
|
146
|
+
decorator: (tag: string): ClassDecorator => (target) => {
|
|
147
|
+
(target as any).__platform = tag;
|
|
148
|
+
},
|
|
149
|
+
args: ["web"],
|
|
150
|
+
})
|
|
151
|
+
.apply();
|
|
152
|
+
|
|
153
|
+
// Choose flavour at runtime
|
|
154
|
+
Decoration.setFlavourResolver(() => "web");
|
|
155
|
+
|
|
156
|
+
const dec = Decoration.flavouredAs("web").for("component").apply();
|
|
157
|
+
|
|
158
|
+
@dec
|
|
159
|
+
class WebComponent {}
|
|
160
|
+
|
|
161
|
+
console.log((WebComponent as any).__platform); // "web"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
3) Override base decorators for a specific flavour
|
|
165
|
+
|
|
166
|
+
Description: Replace default decorators entirely for flavour "mobile".
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { Decoration } from "@decaf-ts/decoration";
|
|
170
|
+
|
|
171
|
+
// Default base
|
|
172
|
+
Decoration.for("component")
|
|
173
|
+
.define(((t: any) => { (t as any).__base = true; }) as ClassDecorator)
|
|
174
|
+
.apply();
|
|
175
|
+
|
|
176
|
+
// Flavour override (no extras needed)
|
|
177
|
+
Decoration.flavouredAs("mobile")
|
|
178
|
+
.for("component")
|
|
179
|
+
.define(((t: any) => { (t as any).__mobile = true; }) as ClassDecorator)
|
|
180
|
+
.apply();
|
|
181
|
+
|
|
182
|
+
Decoration.setFlavourResolver(() => "mobile");
|
|
183
|
+
const dec = Decoration.flavouredAs("mobile").for("component").apply();
|
|
184
|
+
|
|
185
|
+
@dec()
|
|
186
|
+
class MobileComponent {}
|
|
187
|
+
|
|
188
|
+
console.log((MobileComponent as any).__base); // undefined (overridden)
|
|
189
|
+
console.log((MobileComponent as any).__mobile); // true
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Decorator utilities
|
|
193
|
+
|
|
194
|
+
1) metadata(key, value)
|
|
195
|
+
|
|
196
|
+
Description: Attach arbitrary metadata to a class or property.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { metadata, Metadata } from "@decaf-ts/decoration";
|
|
200
|
+
|
|
201
|
+
@metadata("role", "entity")
|
|
202
|
+
class User {
|
|
203
|
+
@((metadata("format", "email") as unknown) as PropertyDecorator)
|
|
204
|
+
email!: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(Metadata.get(User, "role")); // "entity"
|
|
208
|
+
console.log(Metadata.get(User, "format")); // "email"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
2) prop()
|
|
212
|
+
|
|
213
|
+
Description: Record the reflected design type for a property.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import "reflect-metadata";
|
|
217
|
+
import { prop, Metadata } from "@decaf-ts/decoration";
|
|
218
|
+
|
|
219
|
+
class Article {
|
|
220
|
+
@((prop() as unknown) as PropertyDecorator)
|
|
221
|
+
title!: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(Metadata.type(Article, "title") === String); // true
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
3) apply(...decorators)
|
|
228
|
+
|
|
229
|
+
Description: Compose multiple decorators of different kinds.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { apply } from "@decaf-ts/decoration";
|
|
233
|
+
|
|
234
|
+
const dClass: ClassDecorator = (t) => console.log("class", (t as any).name);
|
|
235
|
+
const dProp: PropertyDecorator = (_t, key) => console.log("prop", String(key));
|
|
236
|
+
const dMethod: MethodDecorator = (_t, key) => console.log("method", String(key));
|
|
237
|
+
|
|
238
|
+
@apply(dClass)
|
|
239
|
+
class Box {
|
|
240
|
+
@((apply(dProp) as unknown) as PropertyDecorator)
|
|
241
|
+
size!: number;
|
|
242
|
+
|
|
243
|
+
@((apply(dMethod) as unknown) as MethodDecorator)
|
|
244
|
+
open() {}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
4) propMetadata(key, value)
|
|
249
|
+
|
|
250
|
+
Description: Combine setting arbitrary metadata and capturing the property's design type.
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import { propMetadata, Metadata } from "@decaf-ts/decoration";
|
|
254
|
+
|
|
255
|
+
class Product {
|
|
256
|
+
@propMetadata("column", "price")
|
|
257
|
+
price!: number;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(Metadata.get(Product, "column")); // "price"
|
|
261
|
+
console.log(Metadata.type(Product, "price") === Number); // true
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
5) description(text)
|
|
265
|
+
|
|
266
|
+
Description: Store human-readable documentation for class and property.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { description, Metadata } from "@decaf-ts/decoration";
|
|
270
|
+
|
|
271
|
+
@description("User entity")
|
|
272
|
+
class User {
|
|
273
|
+
@description("Primary email address")
|
|
274
|
+
email!: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(Metadata.description(User)); // "User entity"
|
|
278
|
+
console.log(Metadata.description<User>(User, "email" as any)); // "Primary email address"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Metadata class
|
|
282
|
+
|
|
283
|
+
1) Set and get nested values
|
|
284
|
+
|
|
285
|
+
Description: Use low-level get/set for arbitrary metadata paths.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { Metadata, DecorationKeys } from "@decaf-ts/decoration";
|
|
289
|
+
|
|
290
|
+
class Org {}
|
|
291
|
+
|
|
292
|
+
Metadata.set(Org, `${DecorationKeys.DESCRIPTION}.class`, "Organization");
|
|
293
|
+
Metadata.set(Org, `${DecorationKeys.PROPERTIES}.name`, String);
|
|
294
|
+
|
|
295
|
+
console.log(Metadata.get(Org, `${DecorationKeys.DESCRIPTION}.class`)); // "Organization"
|
|
296
|
+
console.log(Metadata.type(Org, "name") === String); // true
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
2) List known properties
|
|
300
|
+
|
|
301
|
+
Description: Retrieve the keys that have recorded type info.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import { Metadata } from "@decaf-ts/decoration";
|
|
305
|
+
|
|
306
|
+
class File {
|
|
307
|
+
name!: string;
|
|
308
|
+
size!: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Metadata.set(File, "properties.name", String);
|
|
312
|
+
Metadata.set(File, "properties.size", Number);
|
|
313
|
+
|
|
314
|
+
console.log(Metadata.properties(File)); // ["name", "size"]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
3) Mirror control
|
|
318
|
+
|
|
319
|
+
Description: Disable mirroring to the constructor if desired.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { Metadata, DecorationKeys } from "@decaf-ts/decoration";
|
|
323
|
+
|
|
324
|
+
class Temp {}
|
|
325
|
+
;(Metadata as any).mirror = false; // disable
|
|
326
|
+
|
|
327
|
+
Metadata.set(Temp, `${DecorationKeys.DESCRIPTION}.class`, "Temporary");
|
|
328
|
+
|
|
329
|
+
console.log(Object.getOwnPropertyDescriptor(Temp, DecorationKeys.REFLECT)); // undefined
|
|
330
|
+
// Re-enable when done
|
|
331
|
+
;(Metadata as any).mirror = true;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Constants and types
|
|
335
|
+
|
|
336
|
+
Description: Access well-known keys and defaults when interacting with metadata.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
import { DefaultFlavour, ObjectKeySplitter, DecorationKeys } from "@decaf-ts/decoration";
|
|
340
|
+
|
|
341
|
+
console.log(DefaultFlavour); // "decaf"
|
|
342
|
+
console.log(ObjectKeySplitter); // "."
|
|
343
|
+
console.log(DecorationKeys.PROPERTIES); // "properties"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
### Related
|
|
348
|
+
|
|
349
|
+
[](https://github.com/decaf-ts/ts-workspace)
|
|
350
|
+
|
|
351
|
+
### Social
|
|
352
|
+
|
|
353
|
+
[](https://www.linkedin.com/in/decaf-ts/)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
#### Languages
|
|
359
|
+
|
|
360
|
+

|
|
361
|
+

|
|
362
|
+

|
|
363
|
+

|
|
364
|
+
|
|
365
|
+
## Getting help
|
|
366
|
+
|
|
367
|
+
If you have bug reports, questions or suggestions please [create a new issue](https://github.com/decaf-ts/ts-workspace/issues/new/choose).
|
|
368
|
+
|
|
369
|
+
## Contributing
|
|
370
|
+
|
|
371
|
+
I am grateful for any contributions made to this project. Please read [this](./workdocs/98-Contributing.md) to get started.
|
|
372
|
+
|
|
373
|
+
## Supporting
|
|
374
|
+
|
|
375
|
+
The first and easiest way you can support it is by [Contributing](./workdocs/98-Contributing.md). Even just finding a typo in the documentation is important.
|
|
376
|
+
|
|
377
|
+
Financial support is always welcome and helps keep both me and the project alive and healthy.
|
|
378
|
+
|
|
379
|
+
So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
This project is released under the [MIT License](./LICENSE.md).
|
|
384
|
+
|
|
385
|
+
By developers, for developers...
|