@epsilon-asi/foundation 0.0.1-testing

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 (71) hide show
  1. package/.npm/_logs/2026-05-02T22_04_53_277Z-debug-0.log +22 -0
  2. package/.npm/_logs/2026-05-02T22_04_53_601Z-debug-0.log +18 -0
  3. package/.npm/_logs/2026-05-02T22_04_58_522Z-debug-0.log +18 -0
  4. package/.npm/_logs/2026-05-02T22_05_00_812Z-debug-0.log +19 -0
  5. package/.npm/_logs/2026-05-02T22_05_00_961Z-debug-0.log +14 -0
  6. package/build/decorators/factory/clone.d.ts +3 -0
  7. package/build/decorators/factory/clone.js +85 -0
  8. package/build/decorators/factory/decoratorRegistry.d.ts +2 -0
  9. package/build/decorators/factory/decoratorRegistry.js +213 -0
  10. package/build/decorators/factory/examples/accessors.d.ts +25 -0
  11. package/build/decorators/factory/examples/accessors.js +87 -0
  12. package/build/decorators/factory/examples/crud.d.ts +50 -0
  13. package/build/decorators/factory/examples/crud.js +120 -0
  14. package/build/decorators/factory/examples/orm.d.ts +45 -0
  15. package/build/decorators/factory/examples/orm.js +107 -0
  16. package/build/decorators/factory/examples/routing.d.ts +43 -0
  17. package/build/decorators/factory/examples/routing.js +112 -0
  18. package/build/decorators/factory/examples/runExamples.d.ts +1 -0
  19. package/build/decorators/factory/examples/runExamples.js +22 -0
  20. package/build/decorators/factory/examples/serialization.d.ts +24 -0
  21. package/build/decorators/factory/examples/serialization.js +79 -0
  22. package/build/decorators/factory/index.d.ts +3 -0
  23. package/build/decorators/factory/index.js +8 -0
  24. package/build/decorators/factory/snapshot.d.ts +2 -0
  25. package/build/decorators/factory/snapshot.js +75 -0
  26. package/build/decorators/factory/symbolMetadata.d.ts +2 -0
  27. package/build/decorators/factory/symbolMetadata.js +17 -0
  28. package/build/decorators/factory/types.d.ts +123 -0
  29. package/build/decorators/factory/types.js +2 -0
  30. package/build/decorators/validation/validatable.d.ts +0 -0
  31. package/build/decorators/validation/validatable.js +64 -0
  32. package/build/index.d.ts +5 -0
  33. package/build/index.js +12 -0
  34. package/build/package.json +49 -0
  35. package/coverage/clover.xml +220 -0
  36. package/coverage/coverage-final.json +6 -0
  37. package/coverage/lcov-report/base.css +224 -0
  38. package/coverage/lcov-report/block-navigation.js +87 -0
  39. package/coverage/lcov-report/clone.ts.html +352 -0
  40. package/coverage/lcov-report/decoratorRegistry.ts.html +1183 -0
  41. package/coverage/lcov-report/favicon.png +0 -0
  42. package/coverage/lcov-report/index.html +176 -0
  43. package/coverage/lcov-report/index.ts.html +145 -0
  44. package/coverage/lcov-report/prettify.css +1 -0
  45. package/coverage/lcov-report/prettify.js +2 -0
  46. package/coverage/lcov-report/snapshot.ts.html +370 -0
  47. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  48. package/coverage/lcov-report/sorter.js +210 -0
  49. package/coverage/lcov-report/symbolMetadata.ts.html +145 -0
  50. package/coverage/lcov.info +490 -0
  51. package/package.json +47 -0
  52. package/src/decorators/factory/__tests__/cloneAndSnapshot.test.ts +223 -0
  53. package/src/decorators/factory/__tests__/decoratorRegistry.test.ts +292 -0
  54. package/src/decorators/factory/__tests__/decoratorRegistryEdges.test.ts +262 -0
  55. package/src/decorators/factory/clone.ts +89 -0
  56. package/src/decorators/factory/decoratorRegistry.ts +366 -0
  57. package/src/decorators/factory/examples/accessors.ts +38 -0
  58. package/src/decorators/factory/examples/crud.ts +82 -0
  59. package/src/decorators/factory/examples/orm.ts +83 -0
  60. package/src/decorators/factory/examples/routing.ts +70 -0
  61. package/src/decorators/factory/examples/runExamples.ts +22 -0
  62. package/src/decorators/factory/examples/serialization.ts +47 -0
  63. package/src/decorators/factory/index.ts +20 -0
  64. package/src/decorators/factory/snapshot.ts +95 -0
  65. package/src/decorators/factory/symbolMetadata.ts +20 -0
  66. package/src/decorators/factory/type-tests/decoratorRegistry.type-test.ts +302 -0
  67. package/src/decorators/factory/types.ts +259 -0
  68. package/src/decorators/validation/validatable.ts +63 -0
  69. package/src/index.ts +9 -0
  70. package/tsconfig.json +19 -0
  71. package/tsconfig.type-tests.json +8 -0
@@ -0,0 +1,366 @@
1
+ import { immutableClone } from './clone';
2
+ import { createSnapshot } from './snapshot';
3
+ import { symbolMetadata } from './symbolMetadata';
4
+ import type {
5
+ AccessorDecoratorFactory,
6
+ AnyFunction,
7
+ Constructor,
8
+ CreateDecoratorRegistryOptions,
9
+ DecorationKind,
10
+ DecorationRecord,
11
+ DecoratorRegistry,
12
+ DecoratorSchema,
13
+ DirectKeyedMetadataArgs,
14
+ MetadataArgs,
15
+ MetadataOf,
16
+ MethodDecoratorFactory,
17
+ PropertyDecoratorFactory,
18
+ PublicMemberKey,
19
+ RegistryMetadataSnapshot,
20
+ RegistryReadOptions,
21
+ ScopedDecoratorBuilder,
22
+ StandardClassDecorator,
23
+ StringKeyOf,
24
+ TargetOf,
25
+ UnscopedClassDecoratorFactory,
26
+ } from './types';
27
+
28
+ type DecoratorMetadataObject = object;
29
+
30
+ type ClassOrMemberContext =
31
+ | ClassDecoratorContext
32
+ | ClassFieldDecoratorContext
33
+ | ClassMethodDecoratorContext
34
+ | ClassAccessorDecoratorContext
35
+ | ClassGetterDecoratorContext
36
+ | ClassSetterDecoratorContext;
37
+
38
+ type MemberContext = Exclude<ClassOrMemberContext, ClassDecoratorContext>;
39
+
40
+ type ParsedDecoratorArgs = {
41
+ readonly key?: PublicMemberKey;
42
+ readonly metadata: unknown;
43
+ };
44
+
45
+ function isPropertyKey(value: unknown): value is PublicMemberKey {
46
+ return typeof value === 'string' || typeof value === 'symbol';
47
+ }
48
+
49
+ function assertPropertyKey(value: unknown, decoratorName: string): PublicMemberKey {
50
+ if (isPropertyKey(value)) return value;
51
+ throw new TypeError(
52
+ `Decorator "${decoratorName}" expected a string or symbol member key, but received ${typeof value}.`,
53
+ );
54
+ }
55
+
56
+ function parseDirectArgs(args: readonly unknown[], decoratorName: string): ParsedDecoratorArgs {
57
+ if (args.length === 0) {
58
+ return { metadata: undefined };
59
+ }
60
+
61
+ if (args.length === 1) {
62
+ return { metadata: args[0] };
63
+ }
64
+
65
+ return {
66
+ key: assertPropertyKey(args[0], decoratorName),
67
+ metadata: args[1],
68
+ };
69
+ }
70
+
71
+ function parseOnArgs(args: readonly unknown[], decoratorName: string): ParsedDecoratorArgs {
72
+ if (args.length === 0) {
73
+ throw new TypeError(`Decorator "${decoratorName}.on" requires a member key.`);
74
+ }
75
+
76
+ return {
77
+ key: assertPropertyKey(args[0], `${decoratorName}.on`),
78
+ metadata: args.length > 1 ? args[1] : undefined,
79
+ };
80
+ }
81
+
82
+ function assertContextKind(
83
+ expectedKind: DecorationKind,
84
+ decoratorName: string,
85
+ context: ClassOrMemberContext,
86
+ ): void {
87
+ if (expectedKind === 'class' && context.kind === 'class') return;
88
+ if (expectedKind === 'property' && context.kind === 'field') return;
89
+ if (expectedKind === 'method' && context.kind === 'method') return;
90
+ if (
91
+ expectedKind === 'accessor' &&
92
+ (context.kind === 'accessor' || context.kind === 'getter' || context.kind === 'setter')
93
+ ) {
94
+ return;
95
+ }
96
+
97
+ throw new TypeError(
98
+ `Decorator "${decoratorName}" can only be used on ${expectedKind} targets, but was used on ${context.kind}.`,
99
+ );
100
+ }
101
+
102
+ function getTargetConstructor(target: Constructor | object): Constructor | undefined {
103
+ if (typeof target === 'function') {
104
+ return target as Constructor;
105
+ }
106
+
107
+ const constructor = target.constructor;
108
+ return typeof constructor === 'function' ? (constructor as Constructor) : undefined;
109
+ }
110
+
111
+ function getConstructorChain(constructor: Constructor, includeInherited: boolean): Constructor[] {
112
+ if (!includeInherited) return [constructor];
113
+
114
+ const chain: Constructor[] = [];
115
+ let current: unknown = constructor;
116
+
117
+ while (typeof current === 'function' && current !== Function.prototype) {
118
+ chain.push(current as Constructor);
119
+ current = Object.getPrototypeOf(current);
120
+ }
121
+
122
+ return chain.reverse();
123
+ }
124
+
125
+ function getOwnMetadataObject(constructor: Constructor): DecoratorMetadataObject | undefined {
126
+ if (!Object.prototype.hasOwnProperty.call(constructor, symbolMetadata)) {
127
+ return undefined;
128
+ }
129
+
130
+ const metadata = (constructor as unknown as Record<PropertyKey, unknown>)[symbolMetadata];
131
+ return typeof metadata === 'object' && metadata !== null ? metadata : undefined;
132
+ }
133
+
134
+ function samePropertyKey(left: PublicMemberKey, right: PublicMemberKey): boolean {
135
+ return left === right;
136
+ }
137
+
138
+ class ModernDecoratorRegistry<TSchema extends DecoratorSchema>
139
+ implements DecoratorRegistry<TSchema>
140
+ {
141
+ public readonly namespace: string;
142
+ public readonly allowPrivateMembers: boolean;
143
+
144
+ private nextOrder = 0;
145
+ private readonly recordsByMetadata = new WeakMap<DecoratorMetadataObject, DecorationRecord[]>();
146
+
147
+ public constructor(options: CreateDecoratorRegistryOptions = {}) {
148
+ this.namespace = options.namespace ?? 'default';
149
+ this.allowPrivateMembers = options.allowPrivateMembers ?? false;
150
+ }
151
+
152
+ public class<K extends StringKeyOf<TSchema['class']>>(
153
+ name: K,
154
+ ): UnscopedClassDecoratorFactory<MetadataOf<TSchema['class'][K]>> {
155
+ return this.createClassFactory(String(name));
156
+ }
157
+
158
+ public for<TContract>(): ScopedDecoratorBuilder<TSchema, TContract> {
159
+ return Object.freeze({
160
+ class: <K extends StringKeyOf<TSchema['class']>>(name: K) =>
161
+ this.createScopedClassFactory<TContract, MetadataOf<TSchema['class'][K]>>(String(name)),
162
+
163
+ property: <K extends StringKeyOf<TSchema['property']>>(name: K) =>
164
+ this.createPropertyFactory<
165
+ TContract,
166
+ TargetOf<TSchema['property'][K]>,
167
+ MetadataOf<TSchema['property'][K]>
168
+ >(String(name)),
169
+
170
+ method: <K extends StringKeyOf<TSchema['method']>>(name: K) =>
171
+ this.createMethodFactory<
172
+ TContract,
173
+ Extract<TargetOf<TSchema['method'][K]>, AnyFunction>,
174
+ MetadataOf<TSchema['method'][K]>
175
+ >(String(name)),
176
+
177
+ accessor: <K extends StringKeyOf<TSchema['accessor']>>(name: K) =>
178
+ this.createAccessorFactory<
179
+ TContract,
180
+ TargetOf<TSchema['accessor'][K]>,
181
+ MetadataOf<TSchema['accessor'][K]>
182
+ >(String(name)),
183
+ });
184
+ }
185
+
186
+ public read(
187
+ target: Constructor | object,
188
+ options: RegistryReadOptions = {},
189
+ ): RegistryMetadataSnapshot {
190
+ const constructor = getTargetConstructor(target);
191
+ if (constructor === undefined) {
192
+ return createSnapshot([]);
193
+ }
194
+
195
+ const includeInherited = options.includeInherited ?? true;
196
+ const constructors = getConstructorChain(constructor, includeInherited);
197
+ const records: DecorationRecord[] = [];
198
+
199
+ for (const current of constructors) {
200
+ const metadata = getOwnMetadataObject(current);
201
+ if (metadata === undefined) continue;
202
+
203
+ const ownRecords = this.recordsByMetadata.get(metadata);
204
+ if (ownRecords !== undefined) records.push(...ownRecords);
205
+ }
206
+
207
+ return createSnapshot(records);
208
+ }
209
+
210
+ private createClassFactory<TMetadata>(
211
+ decoratorName: string,
212
+ ): UnscopedClassDecoratorFactory<TMetadata> {
213
+ const factory = (<TExpected = unknown>(...args: MetadataArgs<TMetadata>) => {
214
+ const metadata = args.length === 0 ? undefined : args[0];
215
+
216
+ return <TClass extends Constructor<TExpected>>(
217
+ _value: TClass,
218
+ context: ClassDecoratorContext<TClass>,
219
+ ): void => {
220
+ assertContextKind('class', decoratorName, context);
221
+ this.register(context.metadata, {
222
+ kind: 'class',
223
+ decorator: decoratorName,
224
+ metadata,
225
+ static: false,
226
+ private: false,
227
+ });
228
+ };
229
+ }) as UnscopedClassDecoratorFactory<TMetadata>;
230
+
231
+ return factory;
232
+ }
233
+
234
+ private createScopedClassFactory<TContract, TMetadata>(
235
+ decoratorName: string,
236
+ ): StandardClassDecorator<TContract, TMetadata> {
237
+ return ((...args: MetadataArgs<TMetadata>) => {
238
+ const metadata = args.length === 0 ? undefined : args[0];
239
+
240
+ return <TClass extends Constructor<TContract>>(
241
+ _value: TClass,
242
+ context: ClassDecoratorContext<TClass>,
243
+ ): void => {
244
+ assertContextKind('class', decoratorName, context);
245
+ this.register(context.metadata, {
246
+ kind: 'class',
247
+ decorator: decoratorName,
248
+ metadata,
249
+ static: false,
250
+ private: false,
251
+ });
252
+ };
253
+ }) as StandardClassDecorator<TContract, TMetadata>;
254
+ }
255
+
256
+ private createPropertyFactory<TContract, TTarget, TMetadata>(
257
+ decoratorName: string,
258
+ ): PropertyDecoratorFactory<TContract, TTarget, TMetadata> {
259
+ const factory = ((...args: unknown[]) => {
260
+ const parsed = parseDirectArgs(args, decoratorName);
261
+ return this.createMemberDecorator('property', decoratorName, parsed);
262
+ }) as unknown as PropertyDecoratorFactory<TContract, TTarget, TMetadata>;
263
+
264
+ factory.on = ((...args: unknown[]) => {
265
+ const parsed = parseOnArgs(args, decoratorName);
266
+ return this.createMemberDecorator('property', decoratorName, parsed);
267
+ }) as PropertyDecoratorFactory<TContract, TTarget, TMetadata>['on'];
268
+
269
+ return factory;
270
+ }
271
+
272
+ private createMethodFactory<TContract, TTarget extends AnyFunction, TMetadata>(
273
+ decoratorName: string,
274
+ ): MethodDecoratorFactory<TContract, TTarget, TMetadata> {
275
+ const factory = ((...args: unknown[]) => {
276
+ const parsed = parseDirectArgs(args, decoratorName);
277
+ return this.createMemberDecorator('method', decoratorName, parsed);
278
+ }) as unknown as MethodDecoratorFactory<TContract, TTarget, TMetadata>;
279
+
280
+ factory.on = ((...args: unknown[]) => {
281
+ const parsed = parseOnArgs(args, decoratorName);
282
+ return this.createMemberDecorator('method', decoratorName, parsed);
283
+ }) as MethodDecoratorFactory<TContract, TTarget, TMetadata>['on'];
284
+
285
+ return factory;
286
+ }
287
+
288
+ private createAccessorFactory<TContract, TTarget, TMetadata>(
289
+ decoratorName: string,
290
+ ): AccessorDecoratorFactory<TContract, TTarget, TMetadata> {
291
+ const factory = ((...args: unknown[]) => {
292
+ const parsed = parseDirectArgs(args, decoratorName);
293
+ return this.createMemberDecorator('accessor', decoratorName, parsed);
294
+ }) as unknown as AccessorDecoratorFactory<TContract, TTarget, TMetadata>;
295
+
296
+ factory.on = ((...args: unknown[]) => {
297
+ const parsed = parseOnArgs(args, decoratorName);
298
+ return this.createMemberDecorator('accessor', decoratorName, parsed);
299
+ }) as AccessorDecoratorFactory<TContract, TTarget, TMetadata>['on'];
300
+
301
+ return factory;
302
+ }
303
+
304
+ private createMemberDecorator(
305
+ kind: Exclude<DecorationKind, 'class'>,
306
+ decoratorName: string,
307
+ parsed: ParsedDecoratorArgs,
308
+ ) {
309
+ return (_value: unknown, context: MemberContext): void => {
310
+ assertContextKind(kind, decoratorName, context);
311
+
312
+ if (context.private && !this.allowPrivateMembers) {
313
+ throw new Error(
314
+ `Decorator "${decoratorName}" cannot be used on private member "${String(
315
+ context.name,
316
+ )}" unless allowPrivateMembers is true.`,
317
+ );
318
+ }
319
+
320
+ if (parsed.key !== undefined && !samePropertyKey(parsed.key, context.name)) {
321
+ throw new Error(
322
+ `Decorator "${decoratorName}" was keyed for "${String(
323
+ parsed.key,
324
+ )}" but applied to "${String(context.name)}".`,
325
+ );
326
+ }
327
+
328
+ this.register(context.metadata, {
329
+ kind,
330
+ decorator: decoratorName,
331
+ metadata: parsed.metadata,
332
+ memberName: context.name,
333
+ static: context.static,
334
+ private: context.private,
335
+ });
336
+ };
337
+ }
338
+
339
+ private register(
340
+ metadataObject: DecoratorMetadataObject,
341
+ record: Omit<DecorationRecord, 'id' | 'order'>,
342
+ ): void {
343
+ const order = this.nextOrder;
344
+ this.nextOrder += 1;
345
+
346
+ const immutableRecord = Object.freeze({
347
+ ...record,
348
+ id: `${this.namespace}:${record.kind}:${record.decorator}:${order}`,
349
+ order,
350
+ metadata: immutableClone(record.metadata),
351
+ }) as DecorationRecord;
352
+
353
+ const existing = this.recordsByMetadata.get(metadataObject);
354
+ if (existing === undefined) {
355
+ this.recordsByMetadata.set(metadataObject, [immutableRecord]);
356
+ } else {
357
+ existing.push(immutableRecord);
358
+ }
359
+ }
360
+ }
361
+
362
+ export function createDecoratorRegistry<TSchema extends DecoratorSchema>(
363
+ options: CreateDecoratorRegistryOptions = {},
364
+ ): DecoratorRegistry<TSchema> {
365
+ return new ModernDecoratorRegistry<TSchema>(options);
366
+ }
@@ -0,0 +1,38 @@
1
+ import { createDecoratorRegistry } from '../index';
2
+
3
+ type ViewDecorators = {
4
+ class: {};
5
+ property: {};
6
+ method: {};
7
+ accessor: {
8
+ cached: {
9
+ metadata: { ttlMs?: number };
10
+ target: string;
11
+ };
12
+ };
13
+ };
14
+
15
+ type UserViewContract = {
16
+ displayName: string;
17
+ };
18
+
19
+ const registry = createDecoratorRegistry<ViewDecorators>({ namespace: 'accessor-example' });
20
+ const View = registry.for<UserViewContract>();
21
+ const Cached = View.accessor('cached');
22
+
23
+ class UserView implements UserViewContract {
24
+ @Cached('displayName', { ttlMs: 1000 })
25
+ accessor displayName = 'Ray';
26
+ }
27
+
28
+ export function readAccessorMetadata(): { memberName: string | symbol; ttlMs: number | undefined }[] {
29
+ return registry.read(UserView).accessorRecords('cached').map((record) => {
30
+ if (record.memberName === undefined) throw new Error('Missing member name.');
31
+ return {
32
+ memberName: record.memberName,
33
+ ttlMs: (record.metadata as { ttlMs?: number }).ttlMs,
34
+ };
35
+ });
36
+ }
37
+
38
+ export { UserView, registry as accessorRegistry };
@@ -0,0 +1,82 @@
1
+ import { createDecoratorRegistry } from '../index';
2
+
3
+ type CrudController<TEntity, TCreateInput> = {
4
+ id: string;
5
+ get(id: string): Promise<TEntity>;
6
+ create(input: TCreateInput): Promise<TEntity>;
7
+ };
8
+
9
+ type UserDto = {
10
+ id: string;
11
+ email: string;
12
+ };
13
+
14
+ type CreateUserInput = {
15
+ email: string;
16
+ };
17
+
18
+ type UserCrudController = CrudController<UserDto, CreateUserInput>;
19
+
20
+ type CrudDecorators = {
21
+ class: {
22
+ resource: {
23
+ metadata: { name: string };
24
+ };
25
+ };
26
+ property: {
27
+ idField: {
28
+ metadata: undefined;
29
+ target: string;
30
+ };
31
+ };
32
+ method: {
33
+ route: {
34
+ metadata: { method: 'GET' | 'POST'; path: string };
35
+ target: (...args: any[]) => Promise<unknown>;
36
+ };
37
+ };
38
+ accessor: {};
39
+ };
40
+
41
+ const registry = createDecoratorRegistry<CrudDecorators>({ namespace: 'crud-example' });
42
+ const Crud = registry.for<UserCrudController>();
43
+
44
+ const Resource = Crud.class('resource');
45
+ const IdField = Crud.property('idField');
46
+ const Route = Crud.method('route');
47
+
48
+ @Resource({ name: 'users' })
49
+ class UserCrud implements UserCrudController {
50
+ @IdField.on('id')
51
+ id = 'user-1';
52
+
53
+ @Route('get', { method: 'GET', path: '/users/:id' })
54
+ async get(id: string): Promise<UserDto> {
55
+ return { id, email: 'ray@example.com' };
56
+ }
57
+
58
+ @Route.on('create', { method: 'POST', path: '/users' })
59
+ async create(input: CreateUserInput): Promise<UserDto> {
60
+ return { id: 'new-id', email: input.email };
61
+ }
62
+ }
63
+
64
+ export function buildCrudSummary(): { resource: string; routes: string[]; idFields: Array<string | symbol> } {
65
+ const snapshot = registry.read(UserCrud);
66
+ const resource = snapshot.firstClass('resource');
67
+ if (resource === undefined) throw new Error('Missing @Resource metadata.');
68
+
69
+ return {
70
+ resource: (resource.metadata as { name: string }).name,
71
+ routes: snapshot.methodRecords('route').map((record) => {
72
+ const metadata = record.metadata as { method: 'GET' | 'POST'; path: string };
73
+ return `${metadata.method} ${metadata.path}`;
74
+ }),
75
+ idFields: snapshot.propertyRecords('idField').map((record) => {
76
+ if (record.memberName === undefined) throw new Error('Missing member name.');
77
+ return record.memberName;
78
+ }),
79
+ };
80
+ }
81
+
82
+ export { UserCrud, registry as crudRegistry };
@@ -0,0 +1,83 @@
1
+ import { createDecoratorRegistry } from '../index';
2
+
3
+ type OrmDecorators = {
4
+ class: {
5
+ entity: {
6
+ metadata: { table: string };
7
+ };
8
+ };
9
+ property: {
10
+ column: {
11
+ metadata: { type: 'uuid' | 'varchar'; primary?: boolean };
12
+ target: string;
13
+ };
14
+ serializable: {
15
+ metadata: undefined;
16
+ target: unknown;
17
+ };
18
+ };
19
+ method: {};
20
+ accessor: {};
21
+ };
22
+
23
+ type UserModelContract = {
24
+ id: string;
25
+ email: string;
26
+ };
27
+
28
+ const registry = createDecoratorRegistry<OrmDecorators>({ namespace: 'orm-example' });
29
+ const User = registry.for<UserModelContract>();
30
+
31
+ const Entity = User.class('entity');
32
+ const Column = User.property('column');
33
+ const Serializable = User.property('serializable');
34
+
35
+ @Entity({ table: 'users' })
36
+ class UserModel implements UserModelContract {
37
+ @Column('id', { type: 'uuid', primary: true })
38
+ @Serializable.on('id')
39
+ id = 'user-1';
40
+
41
+ @Column.on('email', { type: 'varchar' })
42
+ @Serializable.on('email')
43
+ email = 'ray@example.com';
44
+ }
45
+
46
+ export type TableColumn = {
47
+ name: string | symbol;
48
+ type: 'uuid' | 'varchar';
49
+ primary: boolean;
50
+ };
51
+
52
+ export type TableSchema = {
53
+ table: string;
54
+ columns: TableColumn[];
55
+ serializableFields: Array<string | symbol>;
56
+ };
57
+
58
+ export function buildUserTableSchema(): TableSchema {
59
+ const snapshot = registry.read(UserModel);
60
+ const entity = snapshot.firstClass('entity');
61
+ if (entity === undefined) throw new Error('Missing @Entity metadata.');
62
+
63
+ const tableMetadata = entity.metadata as { table: string };
64
+ const columns = snapshot.propertyRecords('column').map((record) => {
65
+ const metadata = record.metadata as { type: 'uuid' | 'varchar'; primary?: boolean };
66
+ return {
67
+ name: record.memberName ?? 'unknown',
68
+ type: metadata.type,
69
+ primary: metadata.primary ?? false,
70
+ };
71
+ });
72
+
73
+ return {
74
+ table: tableMetadata.table,
75
+ columns,
76
+ serializableFields: snapshot.propertyRecords('serializable').map((record) => {
77
+ if (record.memberName === undefined) throw new Error('Serializable record missing member.');
78
+ return record.memberName;
79
+ }),
80
+ };
81
+ }
82
+
83
+ export { UserModel, registry as ormRegistry };
@@ -0,0 +1,70 @@
1
+ import { createDecoratorRegistry } from '../index';
2
+
3
+ type RouteDecorators = {
4
+ class: {
5
+ controller: {
6
+ metadata: { prefix: string };
7
+ };
8
+ };
9
+ property: {};
10
+ method: {
11
+ route: {
12
+ metadata: { method: 'GET' | 'POST'; path: string };
13
+ target: (...args: any[]) => Promise<unknown>;
14
+ };
15
+ };
16
+ accessor: {};
17
+ };
18
+
19
+ type UserDto = { id: string; email: string };
20
+ type CreateUserInput = { email: string };
21
+
22
+ type UserControllerContract = {
23
+ getUser(id: string): Promise<UserDto>;
24
+ createUser(input: CreateUserInput): Promise<UserDto>;
25
+ };
26
+
27
+ const registry = createDecoratorRegistry<RouteDecorators>({ namespace: 'route-example' });
28
+ const User = registry.for<UserControllerContract>();
29
+
30
+ const Controller = User.class('controller');
31
+ const Route = User.method('route');
32
+
33
+ @Controller({ prefix: '/api' })
34
+ class UserController implements UserControllerContract {
35
+ @Route('getUser', { method: 'GET', path: '/users/:id' })
36
+ async getUser(id: string): Promise<UserDto> {
37
+ return { id, email: 'ray@example.com' };
38
+ }
39
+
40
+ @Route.on('createUser', { method: 'POST', path: '/users' })
41
+ async createUser(input: CreateUserInput): Promise<UserDto> {
42
+ return { id: 'new-id', email: input.email };
43
+ }
44
+ }
45
+
46
+ export type RouteEntry = {
47
+ httpMethod: 'GET' | 'POST';
48
+ path: string;
49
+ handlerName: string | symbol;
50
+ };
51
+
52
+ export function buildRouteTable(): RouteEntry[] {
53
+ const snapshot = registry.read(UserController);
54
+ const controller = snapshot.firstClass('controller');
55
+ if (controller === undefined) throw new Error('Missing @Controller metadata.');
56
+
57
+ const prefix = (controller.metadata as { prefix: string }).prefix;
58
+
59
+ return snapshot.methodRecords('route').map((record) => {
60
+ if (record.memberName === undefined) throw new Error('Route record missing member.');
61
+ const metadata = record.metadata as { method: 'GET' | 'POST'; path: string };
62
+ return {
63
+ httpMethod: metadata.method,
64
+ path: `${prefix}${metadata.path}`,
65
+ handlerName: record.memberName,
66
+ };
67
+ });
68
+ }
69
+
70
+ export { UserController, registry as routeRegistry };
@@ -0,0 +1,22 @@
1
+ import { readAccessorMetadata } from './accessors';
2
+ import { buildCrudSummary } from './crud';
3
+ import { buildUserTableSchema } from './orm';
4
+ import { buildRouteTable } from './routing';
5
+ import { serializeUser } from './serialization';
6
+
7
+ function stable(value: unknown): string {
8
+ return JSON.stringify(value, (_key, innerValue) => {
9
+ if (typeof innerValue === 'symbol') return innerValue.toString();
10
+ return innerValue;
11
+ });
12
+ }
13
+
14
+ const outputs = {
15
+ orm: buildUserTableSchema(),
16
+ routing: buildRouteTable(),
17
+ serialization: serializeUser(),
18
+ crud: buildCrudSummary(),
19
+ accessors: readAccessorMetadata(),
20
+ };
21
+
22
+ console.log(stable(outputs));