@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.
Files changed (41) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +385 -0
  3. package/dist/decoration.cjs +649 -0
  4. package/dist/decoration.esm.cjs +633 -0
  5. package/lib/constants.cjs +64 -0
  6. package/lib/constants.d.ts +58 -0
  7. package/lib/decoration/Decoration.cjs +270 -0
  8. package/lib/decoration/Decoration.d.ts +196 -0
  9. package/lib/decoration/index.cjs +19 -0
  10. package/lib/decoration/index.d.ts +2 -0
  11. package/lib/decoration/types.cjs +3 -0
  12. package/lib/decoration/types.d.ts +95 -0
  13. package/lib/decorators.cjs +97 -0
  14. package/lib/decorators.d.ts +57 -0
  15. package/lib/esm/constants.d.ts +58 -0
  16. package/lib/esm/constants.js +61 -0
  17. package/lib/esm/decoration/Decoration.d.ts +196 -0
  18. package/lib/esm/decoration/Decoration.js +266 -0
  19. package/lib/esm/decoration/index.d.ts +2 -0
  20. package/lib/esm/decoration/index.js +3 -0
  21. package/lib/esm/decoration/types.d.ts +95 -0
  22. package/lib/esm/decoration/types.js +2 -0
  23. package/lib/esm/decorators.d.ts +57 -0
  24. package/lib/esm/decorators.js +90 -0
  25. package/lib/esm/index.d.ts +21 -0
  26. package/lib/esm/index.js +22 -0
  27. package/lib/esm/metadata/Metadata.d.ts +99 -0
  28. package/lib/esm/metadata/Metadata.js +199 -0
  29. package/lib/esm/metadata/index.d.ts +2 -0
  30. package/lib/esm/metadata/index.js +3 -0
  31. package/lib/esm/metadata/types.d.ts +16 -0
  32. package/lib/esm/metadata/types.js +2 -0
  33. package/lib/index.cjs +39 -0
  34. package/lib/index.d.ts +21 -0
  35. package/lib/metadata/Metadata.cjs +203 -0
  36. package/lib/metadata/Metadata.d.ts +99 -0
  37. package/lib/metadata/index.cjs +19 -0
  38. package/lib/metadata/index.d.ts +2 -0
  39. package/lib/metadata/types.cjs +4 -0
  40. package/lib/metadata/types.d.ts +16 -0
  41. 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
+ [![Banner](./workdocs/assets/Banner.png)](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
+ ![Licence](https://img.shields.io/github/license/decaf-ts/ts-workspace.svg?style=plastic)
8
+ ![GitHub language count](https://img.shields.io/github/languages/count/decaf-ts/ts-workspace?style=plastic)
9
+ ![GitHub top language](https://img.shields.io/github/languages/top/decaf-ts/ts-workspace?style=plastic)
10
+
11
+ [![Build & Test](https://github.com/decaf-ts/ts-workspace/actions/workflows/nodejs-build-prod.yaml/badge.svg)](https://github.com/decaf-ts/ts-workspace/actions/workflows/nodejs-build-prod.yaml)
12
+ [![CodeQL](https://github.com/decaf-ts/ts-workspace/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/decaf-ts/ts-workspace/actions/workflows/codeql-analysis.yml)[![Snyk Analysis](https://github.com/decaf-ts/ts-workspace/actions/workflows/snyk-analysis.yaml/badge.svg)](https://github.com/decaf-ts/ts-workspace/actions/workflows/snyk-analysis.yaml)
13
+ [![Pages builder](https://github.com/decaf-ts/ts-workspace/actions/workflows/pages.yaml/badge.svg)](https://github.com/decaf-ts/ts-workspace/actions/workflows/pages.yaml)
14
+ [![.github/workflows/release-on-tag.yaml](https://github.com/decaf-ts/ts-workspace/actions/workflows/release-on-tag.yaml/badge.svg?event=release)](https://github.com/decaf-ts/ts-workspace/actions/workflows/release-on-tag.yaml)
15
+
16
+ ![Open Issues](https://img.shields.io/github/issues/decaf-ts/ts-workspace.svg)
17
+ ![Closed Issues](https://img.shields.io/github/issues-closed/decaf-ts/ts-workspace.svg)
18
+ ![Pull Requests](https://img.shields.io/github/issues-pr-closed/decaf-ts/ts-workspace.svg)
19
+ ![Maintained](https://img.shields.io/badge/Maintained%3F-yes-green.svg)
20
+
21
+ ![Forks](https://img.shields.io/github/forks/decaf-ts/ts-workspace.svg)
22
+ ![Stars](https://img.shields.io/github/stars/decaf-ts/ts-workspace.svg)
23
+ ![Watchers](https://img.shields.io/github/watchers/decaf-ts/ts-workspace.svg)
24
+
25
+ ![Node Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbadges%2Fshields%2Fmaster%2Fpackage.json&label=Node&query=$.engines.node&colorB=blue)
26
+ ![NPM Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbadges%2Fshields%2Fmaster%2Fpackage.json&label=NPM&query=$.engines.npm&colorB=purple)
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
+ [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=decaf-ts&repo=ts-workspace)](https://github.com/decaf-ts/ts-workspace)
350
+
351
+ ### Social
352
+
353
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/decaf-ts/)
354
+
355
+
356
+
357
+
358
+ #### Languages
359
+
360
+ ![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
361
+ ![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
362
+ ![NodeJS](https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white)
363
+ ![ShellScript](https://img.shields.io/badge/Shell_Script-121011?style=for-the-badge&logo=gnu-bash&logoColor=white)
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...