@decaf-ts/decoration 0.0.6 → 0.0.7

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/README.md CHANGED
@@ -79,175 +79,186 @@ Design highlights
79
79
 
80
80
  # How to Use
81
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.
82
+ Practical examples for every exported surface of **@decaf-ts/decoration**. All snippets are TypeScript and mirror the behaviour covered by the unit and integration tests.
83
83
 
84
- Prerequisites
84
+ ## Prerequisites
85
85
 
86
- - Enable experimental decorators and emit decorator metadata in tsconfig.json:
86
+ - Enable experimental decorators and decorator metadata in `tsconfig.json`:
87
87
 
88
- ```json
89
- {
90
- "compilerOptions": {
91
- "experimentalDecorators": true,
92
- "emitDecoratorMetadata": true
88
+ ```json
89
+ {
90
+ "compilerOptions": {
91
+ "experimentalDecorators": true,
92
+ "emitDecoratorMetadata": true
93
+ }
93
94
  }
94
- }
95
- ```
95
+ ```
96
96
 
97
- - Import reflect-metadata once in your test/app entry:
97
+ - Import `reflect-metadata` once (before decorators execute):
98
98
 
99
- ```ts
100
- import "reflect-metadata";
101
- ```
99
+ ```ts
100
+ import "reflect-metadata";
101
+ ```
102
102
 
103
- Decoration class
103
+ ## Decoration Builder
104
104
 
105
- 1) Define base decorators for the default flavour ("decaf")
105
+ The `Decoration` class exposes a fluent builder that lets you define base decorators, add flavour-specific extras, or override behaviour entirely.
106
106
 
107
- Description: Create a decoration pipeline for key "component" that applies two decorators to any class.
107
+ ### 1. Register base decorators for the default flavour
108
108
 
109
109
  ```ts
110
110
  import { Decoration } from "@decaf-ts/decoration";
111
111
 
112
- // A simple class decorator factory (just logs)
113
- const logFactory = (tag: string): ClassDecorator => (target) => {
114
- console.log(`[${tag}]`, (target as any).name);
112
+ const markAsComponent: ClassDecorator = (target) => {
113
+ (target as any).__isComponent = true;
115
114
  };
116
- const mark: ClassDecorator = (t) => {
117
- (t as any).__mark = true;
115
+
116
+ const tagFactory = (tag: string): ClassDecorator => (target) => {
117
+ (target as any).__tag = tag;
118
118
  };
119
119
 
120
- // Register base decorators for the default flavour
121
- const component = Decoration.for("component")
122
- .define({ decorator: logFactory, args: ["base"] }, mark)
123
- .apply();
120
+ const component = () =>
121
+ Decoration.for("component")
122
+ .define({ decorator: tagFactory, args: ["base"] }, markAsComponent)
123
+ .apply();
124
124
 
125
- // Use it
126
- @component
127
- class MyComponent {}
128
- ```
125
+ @component()
126
+ class DefaultComponent {}
129
127
 
130
- 2) Extend base decorators with flavour-specific extras
128
+ (DefaultComponent as any).__isComponent; // true
129
+ (DefaultComponent as any).__tag; // "base"
130
+ ```
131
131
 
132
- Description: Provide extra behavior when the runtime flavour is resolved to "web" while keeping default base decorators.
132
+ ### 2. Extend base decorators with flavour-specific extras
133
133
 
134
134
  ```ts
135
- import { Decoration } from "@decaf-ts/decoration";
135
+ // Register the same base behaviour as above.
136
+ const baseComponent = () =>
137
+ Decoration.for("component")
138
+ .define(((target: any) => target) as ClassDecorator)
139
+ .apply();
136
140
 
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");
141
+ @baseComponent()
142
+ class BaseComponent {}
155
143
 
156
- const dec = Decoration.flavouredAs("web").for("component").apply();
144
+ Decoration.setFlavourResolver(() => "web");
157
145
 
158
- @dec
146
+ const decorate = () =>
147
+ Decoration.flavouredAs("web")
148
+ .for("component")
149
+ .extend({
150
+ decorator: (platform: string): ClassDecorator => (target) => {
151
+ (target as any).__platform = platform;
152
+ },
153
+ args: ["web"],
154
+ })
155
+ .apply();
156
+
157
+ @decorate()
159
158
  class WebComponent {}
160
159
 
161
- console.log((WebComponent as any).__platform); // "web"
160
+ (WebComponent as any).__platform; // "web"
162
161
  ```
163
162
 
164
- 3) Override base decorators for a specific flavour
165
-
166
- Description: Replace default decorators entirely for flavour "mobile".
163
+ ### 3. Override decorators for an alternate flavour
167
164
 
168
165
  ```ts
169
- import { Decoration } from "@decaf-ts/decoration";
166
+ const base = () =>
167
+ Decoration.for("component")
168
+ .define(((target: any) => {
169
+ (target as any).__base = true;
170
+ }) as ClassDecorator)
171
+ .apply();
170
172
 
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();
173
+ @base()
174
+ class BaseBehaviour {}
181
175
 
182
176
  Decoration.setFlavourResolver(() => "mobile");
183
- const dec = Decoration.flavouredAs("mobile").for("component").apply();
184
177
 
185
- @dec()
178
+ const mobileComponent = () =>
179
+ Decoration.flavouredAs("mobile")
180
+ .for("component")
181
+ .define(((target: any) => {
182
+ (target as any).__mobile = true;
183
+ }) as ClassDecorator)
184
+ .apply();
185
+
186
+ @mobileComponent()
186
187
  class MobileComponent {}
187
188
 
188
- console.log((MobileComponent as any).__base); // undefined (overridden)
189
- console.log((MobileComponent as any).__mobile); // true
189
+ (MobileComponent as any).__base; // undefined overridden
190
+ (MobileComponent as any).__mobile; // true
191
+ ```
192
+
193
+ ### 4. Enforce builder guard rails
194
+
195
+ The builder throws when misused; tests assert these guards and you can rely on them in your own code.
196
+
197
+ ```ts
198
+ const base = Decoration.for("guarded");
199
+
200
+ // Missing key before define/extend
201
+ expect(() => (new Decoration() as any).define(() => () => undefined)).toThrow();
202
+
203
+ // Multiple overridable decorators are rejected
204
+ const overridable = {
205
+ decorator: (() => ((target: any) => target)) as any,
206
+ args: [],
207
+ };
208
+ expect(() => base.define(overridable as any, overridable as any)).toThrow();
209
+
210
+ // Extending the default flavour is blocked
211
+ expect(() => Decoration.for("guarded").extend(((t: any) => t) as any)).toThrow();
190
212
  ```
191
213
 
192
- Decorator utilities
214
+ ## Decorator Utilities
193
215
 
194
- 1) metadata(key, value)
216
+ Helper factories under `@decaf-ts/decoration` push metadata into the shared store.
195
217
 
196
- Description: Attach arbitrary metadata to a class or property.
218
+ ### metadata(key, value)
197
219
 
198
220
  ```ts
199
221
  import { metadata, Metadata } from "@decaf-ts/decoration";
200
222
 
201
223
  @metadata("role", "entity")
202
- class User {
203
- @((metadata("format", "email") as unknown) as PropertyDecorator)
204
- email!: string;
205
- }
224
+ class User {}
206
225
 
207
- console.log(Metadata.get(User, "role")); // "entity"
208
- console.log(Metadata.get(User, "format")); // "email"
226
+ Metadata.get(User, "role"); // "entity"
209
227
  ```
210
228
 
211
- 2) prop()
212
-
213
- Description: Record the reflected design type for a property.
229
+ ### prop()
214
230
 
215
231
  ```ts
216
- import "reflect-metadata";
217
232
  import { prop, Metadata } from "@decaf-ts/decoration";
218
233
 
219
234
  class Article {
220
- @((prop() as unknown) as PropertyDecorator)
235
+ @prop()
221
236
  title!: string;
222
237
  }
223
238
 
224
- console.log(Metadata.type(Article, "title") === String); // true
239
+ Metadata.type(Article, "title") === String; // true
225
240
  ```
226
241
 
227
- 3) apply(...decorators)
228
-
229
- Description: Compose multiple decorators of different kinds.
242
+ ### apply(...decorators)
230
243
 
231
244
  ```ts
232
245
  import { apply } from "@decaf-ts/decoration";
233
246
 
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));
247
+ const logClass: ClassDecorator = (target) => {
248
+ console.log("class", (target as any).name);
249
+ };
250
+
251
+ const withLogging = () => apply(logClass);
252
+ const logProperty = () => apply((_, key) => console.log("prop", String(key)));
237
253
 
238
- @apply(dClass)
254
+ @withLogging()
239
255
  class Box {
240
- @((apply(dProp) as unknown) as PropertyDecorator)
256
+ @logProperty()
241
257
  size!: number;
242
-
243
- @((apply(dMethod) as unknown) as MethodDecorator)
244
- open() {}
245
258
  }
246
259
  ```
247
260
 
248
- 4) propMetadata(key, value)
249
-
250
- Description: Combine setting arbitrary metadata and capturing the property's design type.
261
+ ### propMetadata(key, value)
251
262
 
252
263
  ```ts
253
264
  import { propMetadata, Metadata } from "@decaf-ts/decoration";
@@ -257,13 +268,11 @@ class Product {
257
268
  price!: number;
258
269
  }
259
270
 
260
- console.log(Metadata.get(Product, "column")); // "price"
261
- console.log(Metadata.type(Product, "price") === Number); // true
271
+ Metadata.get(Product, "column"); // "price"
272
+ Metadata.type(Product, "price") === Number; // true
262
273
  ```
263
274
 
264
- 5) description(text)
265
-
266
- Description: Store human-readable documentation for class and property.
275
+ ### description(text)
267
276
 
268
277
  ```ts
269
278
  import { description, Metadata } from "@decaf-ts/decoration";
@@ -274,10 +283,93 @@ class User {
274
283
  email!: string;
275
284
  }
276
285
 
277
- console.log(Metadata.description(User)); // "User entity"
278
- console.log(Metadata.description<User>(User, "email" as any)); // "Primary email address"
286
+ Metadata.description(User); // "User entity"
287
+ Metadata.description<User>(User, "email" as keyof User); // "Primary email address"
288
+ ```
289
+
290
+ ## Metadata Runtime Helpers
291
+
292
+ `Metadata` centralises all recorded information. The snippets below exercise the same flows as `metadata.test.ts` and the integration suite.
293
+
294
+ ### Set and read nested values with constructor mirroring
295
+
296
+ ```ts
297
+ import { Metadata, DecorationKeys } from "@decaf-ts/decoration";
298
+
299
+ class Person {
300
+ name!: string;
301
+ }
302
+
303
+ Metadata.set(Person, `${DecorationKeys.DESCRIPTION}.class`, "Person model");
304
+ Metadata.set(Person, `${DecorationKeys.PROPERTIES}.name`, String);
305
+
306
+ Metadata.description(Person); // "Person model"
307
+ Metadata.properties(Person); // ["name"]
308
+
309
+ const mirror = Object.getOwnPropertyDescriptor(Person, DecorationKeys.REFLECT);
310
+ mirror?.enumerable; // false
311
+ ```
312
+
313
+ ### Opt out of mirroring
314
+
315
+ ```ts
316
+ (Metadata as any).mirror = false;
317
+
318
+ Metadata.set(Person, `${DecorationKeys.DESCRIPTION}.class`, "No mirror");
319
+ Object.getOwnPropertyDescriptor(Person, DecorationKeys.REFLECT); // undefined
320
+
321
+ (Metadata as any).mirror = true; // reset when you are done
322
+ ```
323
+
324
+ ### Work with method metadata
325
+
326
+ ```ts
327
+ class Service {
328
+ get(): string {
329
+ return "value";
330
+ }
331
+ }
332
+
333
+ Metadata.set(
334
+ Service,
335
+ `${DecorationKeys.METHODS}.get.${DecorationKeys.DESIGN_PARAMS}`,
336
+ []
337
+ );
338
+ Metadata.set(
339
+ Service,
340
+ `${DecorationKeys.METHODS}.get.${DecorationKeys.DESIGN_RETURN}`,
341
+ String
342
+ );
343
+
344
+ Metadata.methods(Service); // ["get"]
345
+ Metadata.params(Service, "get"); // []
346
+ Metadata.return(Service, "get") === String; // true
347
+ ```
348
+
349
+ ### Leverage convenience accessors
350
+
351
+ ```ts
352
+ Metadata.type(Person, "name"); // Reflects design type recorded by @prop()
353
+ Metadata.get(Person); // Full metadata payload for advanced inspection
354
+ Metadata.get(Person, DecorationKeys.CONSTRUCTOR); // Underlying constructor reference
355
+ ```
356
+
357
+ ## Library Registration
358
+
359
+ Prevent duplicate registration of flavour libraries via `Metadata.registerLibrary`.
360
+
361
+ ```ts
362
+ import { Metadata } from "@decaf-ts/decoration";
363
+
364
+ Metadata.registerLibrary("@decaf-ts/decoration", "0.0.6");
365
+
366
+ expect(() =>
367
+ Metadata.registerLibrary("@decaf-ts/decoration", "0.0.6")
368
+ ).toThrow(/already/);
279
369
  ```
280
370
 
371
+ You now have end-to-end examples for every public API: builder setup, decorator helpers, metadata management, and library bookkeeping. Mirror the test suite for additional inspiration when adding new patterns.
372
+
281
373
  Metadata class
282
374
 
283
375
  1) Set and get nested values