@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,47 @@
1
+ import { createDecoratorRegistry } from '../index';
2
+
3
+ type SerializationDecorators = {
4
+ class: {};
5
+ property: {
6
+ serializable: {
7
+ metadata: undefined;
8
+ target: unknown;
9
+ };
10
+ };
11
+ method: {};
12
+ accessor: {};
13
+ };
14
+
15
+ type UserContract = {
16
+ id: string;
17
+ email: string;
18
+ passwordHash: string;
19
+ };
20
+
21
+ const registry = createDecoratorRegistry<SerializationDecorators>({ namespace: 'serialization-example' });
22
+ const User = registry.for<UserContract>();
23
+ const Serializable = User.property('serializable');
24
+
25
+ class UserRecord implements UserContract {
26
+ @Serializable.on('id')
27
+ id = 'user-1';
28
+
29
+ @Serializable.on('email')
30
+ email = 'ray@example.com';
31
+
32
+ passwordHash = 'not-for-output';
33
+ }
34
+
35
+ export function serializeUser(user = new UserRecord()): Record<string, unknown> {
36
+ const snapshot = registry.read(user);
37
+ const output: Record<string, unknown> = {};
38
+
39
+ for (const record of snapshot.propertyRecords('serializable')) {
40
+ if (typeof record.memberName !== 'string') continue;
41
+ output[record.memberName] = (user as unknown as Record<string, unknown>)[record.memberName];
42
+ }
43
+
44
+ return output;
45
+ }
46
+
47
+ export { UserRecord, registry as serializationRegistry };
@@ -0,0 +1,20 @@
1
+ export { createDecoratorRegistry } from './decoratorRegistry';
2
+ export { ensureSymbolMetadata, symbolMetadata } from './symbolMetadata';
3
+ export type {
4
+ AccessorDecoratorDefinition,
5
+ AnyFunction,
6
+ ClassDecoratorDefinition,
7
+ Constructor,
8
+ CreateDecoratorRegistryOptions,
9
+ DecorationKind,
10
+ DecorationRecord,
11
+ DecoratorRegistry,
12
+ DecoratorSchema,
13
+ MethodDecoratorDefinition,
14
+ MetadataOf,
15
+ PropertyDecoratorDefinition,
16
+ RegistryMetadataSnapshot,
17
+ RegistryReadOptions,
18
+ ScopedDecoratorBuilder,
19
+ TargetOf,
20
+ } from './types';
@@ -0,0 +1,95 @@
1
+ import type { DecorationKind, DecorationRecord, RegistryMetadataSnapshot } from './types';
2
+
3
+ function cloneRecords(records: readonly DecorationRecord[]): readonly DecorationRecord[] {
4
+ return Object.freeze(records.slice());
5
+ }
6
+
7
+ function filterRecords(
8
+ records: readonly DecorationRecord[],
9
+ kind: DecorationKind,
10
+ decorator?: string,
11
+ ): readonly DecorationRecord[] {
12
+ return Object.freeze(
13
+ records.filter((record) => {
14
+ if (record.kind !== kind) return false;
15
+ return decorator === undefined || record.decorator === decorator;
16
+ }),
17
+ );
18
+ }
19
+
20
+ function memberMap(
21
+ records: readonly DecorationRecord[],
22
+ kind: 'property' | 'method' | 'accessor',
23
+ decorator?: string,
24
+ ): ReadonlyMap<string | symbol, readonly DecorationRecord[]> {
25
+ const mutable = new Map<string | symbol, DecorationRecord[]>();
26
+
27
+ for (const record of records) {
28
+ if (record.kind !== kind) continue;
29
+ if (decorator !== undefined && record.decorator !== decorator) continue;
30
+ if (record.memberName === undefined) continue;
31
+
32
+ const existing = mutable.get(record.memberName);
33
+ if (existing === undefined) {
34
+ mutable.set(record.memberName, [record]);
35
+ } else {
36
+ existing.push(record);
37
+ }
38
+ }
39
+
40
+ const frozen = new Map<string | symbol, readonly DecorationRecord[]>();
41
+ for (const [key, value] of mutable.entries()) {
42
+ frozen.set(key, Object.freeze(value.slice()));
43
+ }
44
+
45
+ return frozen;
46
+ }
47
+
48
+ export function createSnapshot(records: readonly DecorationRecord[]): RegistryMetadataSnapshot {
49
+ const sorted = records.slice().sort((left, right) => left.order - right.order);
50
+ const all = cloneRecords(sorted);
51
+ const classes = filterRecords(all, 'class');
52
+ const properties = filterRecords(all, 'property');
53
+ const methods = filterRecords(all, 'method');
54
+ const accessors = filterRecords(all, 'accessor');
55
+
56
+ return Object.freeze({
57
+ all,
58
+ classes,
59
+ properties,
60
+ methods,
61
+ accessors,
62
+
63
+ firstClass(decorator: string) {
64
+ return classes.find((record) => record.decorator === decorator);
65
+ },
66
+
67
+ classRecords(decorator?: string) {
68
+ return filterRecords(all, 'class', decorator);
69
+ },
70
+
71
+ propertyRecords(decorator?: string) {
72
+ return filterRecords(all, 'property', decorator);
73
+ },
74
+
75
+ methodRecords(decorator?: string) {
76
+ return filterRecords(all, 'method', decorator);
77
+ },
78
+
79
+ accessorRecords(decorator?: string) {
80
+ return filterRecords(all, 'accessor', decorator);
81
+ },
82
+
83
+ propertyMap(decorator?: string) {
84
+ return memberMap(all, 'property', decorator);
85
+ },
86
+
87
+ methodMap(decorator?: string) {
88
+ return memberMap(all, 'method', decorator);
89
+ },
90
+
91
+ accessorMap(decorator?: string) {
92
+ return memberMap(all, 'accessor', decorator);
93
+ },
94
+ });
95
+ }
@@ -0,0 +1,20 @@
1
+ type SymbolConstructorWithMetadata = SymbolConstructor & {
2
+ metadata?: symbol;
3
+ };
4
+
5
+ export function ensureSymbolMetadata(): symbol {
6
+ const symbolConstructor = Symbol as SymbolConstructorWithMetadata;
7
+
8
+ if (symbolConstructor.metadata === undefined) {
9
+ Object.defineProperty(symbolConstructor, 'metadata', {
10
+ configurable: false,
11
+ enumerable: false,
12
+ writable: false,
13
+ value: Symbol('Symbol.metadata'),
14
+ });
15
+ }
16
+
17
+ return symbolConstructor.metadata;
18
+ }
19
+
20
+ export const symbolMetadata = ensureSymbolMetadata();
@@ -0,0 +1,302 @@
1
+ import { createDecoratorRegistry } from '../index';
2
+
3
+ type UserDto = {
4
+ id: string;
5
+ email: string;
6
+ age: number;
7
+ };
8
+
9
+ type CreateUserInput = {
10
+ email: string;
11
+ age: number;
12
+ };
13
+
14
+ interface UserControllerContract {
15
+ id: string;
16
+ email: string;
17
+ age: number;
18
+ getUser(id: string): Promise<UserDto>;
19
+ createUser(input: CreateUserInput): Promise<UserDto>;
20
+ }
21
+
22
+ type AppDecorators = {
23
+ class: {
24
+ entity: {
25
+ metadata: { table: string };
26
+ };
27
+ };
28
+ property: {
29
+ column: {
30
+ metadata: { type: 'uuid' | 'varchar'; primary?: boolean };
31
+ target: string;
32
+ };
33
+ serializable: {
34
+ metadata: undefined;
35
+ target: unknown;
36
+ };
37
+ };
38
+ method: {
39
+ route: {
40
+ metadata: { method: 'GET' | 'POST'; path: string };
41
+ target: (...args: any[]) => Promise<unknown>;
42
+ };
43
+ };
44
+ accessor: {
45
+ cached: {
46
+ metadata: { ttlMs?: number };
47
+ target: string;
48
+ };
49
+ };
50
+ };
51
+
52
+ const registry = createDecoratorRegistry<AppDecorators>();
53
+ const Entity = registry.class('entity');
54
+ const User = registry.for<UserControllerContract>();
55
+ const ScopedEntity = User.class('entity');
56
+ const Column = User.property('column');
57
+ const Serializable = User.property('serializable');
58
+ const Route = User.method('route');
59
+ const Cached = User.accessor('cached');
60
+
61
+ @Entity<UserControllerContract>({ table: 'users' })
62
+ class GoodUnscopedUserController implements UserControllerContract {
63
+ id!: string;
64
+ email!: string;
65
+ age!: number;
66
+
67
+ async getUser(id: string): Promise<UserDto> {
68
+ return { id, email: this.email, age: this.age };
69
+ }
70
+
71
+ async createUser(input: CreateUserInput): Promise<UserDto> {
72
+ return { id: 'new-id', email: input.email, age: input.age };
73
+ }
74
+ }
75
+
76
+ // @ts-expect-error missing contract members
77
+ @Entity<UserControllerContract>({ table: 'users' })
78
+ class BadUnscopedUserController {}
79
+
80
+ // @ts-expect-error table must be string
81
+ @Entity<UserControllerContract>({ table: 123 })
82
+ class BadClassMetadataController implements UserControllerContract {
83
+ id!: string;
84
+ email!: string;
85
+ age!: number;
86
+
87
+ async getUser(id: string): Promise<UserDto> {
88
+ return { id, email: this.email, age: this.age };
89
+ }
90
+
91
+ async createUser(input: CreateUserInput): Promise<UserDto> {
92
+ return { id: 'new-id', email: input.email, age: input.age };
93
+ }
94
+ }
95
+
96
+ @ScopedEntity({ table: 'users' })
97
+ class GoodScopedUserController implements UserControllerContract {
98
+ @Column({ type: 'uuid', primary: true })
99
+ id!: string;
100
+
101
+ @Serializable()
102
+ @Column('email', { type: 'varchar' })
103
+ email!: string;
104
+
105
+ age!: number;
106
+
107
+ @Route({ method: 'GET', path: '/users/:id' })
108
+ async getUser(id: string): Promise<UserDto> {
109
+ return { id, email: this.email, age: this.age };
110
+ }
111
+
112
+ @Route.on('createUser', { method: 'POST', path: '/users' })
113
+ async createUser(input: CreateUserInput): Promise<UserDto> {
114
+ return { id: 'new-id', email: input.email, age: input.age };
115
+ }
116
+ }
117
+
118
+ class BadPropertyMetadata implements UserControllerContract {
119
+ // @ts-expect-error type must be uuid or varchar
120
+ @Column({ type: 'int' })
121
+ id!: string;
122
+
123
+ email!: string;
124
+ age!: number;
125
+
126
+ async getUser(id: string): Promise<UserDto> {
127
+ return { id, email: this.email, age: this.age };
128
+ }
129
+
130
+ async createUser(input: CreateUserInput): Promise<UserDto> {
131
+ return { id: 'new-id', email: input.email, age: input.age };
132
+ }
133
+ }
134
+
135
+ class BadPropertyTarget implements UserControllerContract {
136
+ id!: string;
137
+ email!: string;
138
+
139
+ // @ts-expect-error column target is string but age is number
140
+ @Column({ type: 'varchar' })
141
+ age!: number;
142
+
143
+ async getUser(id: string): Promise<UserDto> {
144
+ return { id, email: this.email, age: this.age };
145
+ }
146
+
147
+ async createUser(input: CreateUserInput): Promise<UserDto> {
148
+ return { id: 'new-id', email: input.email, age: input.age };
149
+ }
150
+ }
151
+
152
+ class BadPropertyKey implements UserControllerContract {
153
+ // @ts-expect-error decorator key does not match member name
154
+ @Column('email', { type: 'varchar' })
155
+ id!: string;
156
+
157
+ email!: string;
158
+ age!: number;
159
+
160
+ async getUser(id: string): Promise<UserDto> {
161
+ return { id, email: this.email, age: this.age };
162
+ }
163
+
164
+ async createUser(input: CreateUserInput): Promise<UserDto> {
165
+ return { id: 'new-id', email: input.email, age: input.age };
166
+ }
167
+ }
168
+
169
+ class BadNoMetadataArgs implements UserControllerContract {
170
+ id!: string;
171
+
172
+ // @ts-expect-error Serializable takes no metadata
173
+ @Serializable({ enabled: true })
174
+ email!: string;
175
+
176
+ age!: number;
177
+
178
+ async getUser(id: string): Promise<UserDto> {
179
+ return { id, email: this.email, age: this.age };
180
+ }
181
+
182
+ async createUser(input: CreateUserInput): Promise<UserDto> {
183
+ return { id: 'new-id', email: input.email, age: input.age };
184
+ }
185
+ }
186
+
187
+ class BadRequiredMetadataArgs implements UserControllerContract {
188
+ // @ts-expect-error Column requires metadata
189
+ @Column()
190
+ id!: string;
191
+
192
+ email!: string;
193
+ age!: number;
194
+
195
+ async getUser(id: string): Promise<UserDto> {
196
+ return { id, email: this.email, age: this.age };
197
+ }
198
+
199
+ async createUser(input: CreateUserInput): Promise<UserDto> {
200
+ return { id: 'new-id', email: input.email, age: input.age };
201
+ }
202
+ }
203
+
204
+ class BadMethodMetadata implements UserControllerContract {
205
+ id!: string;
206
+ email!: string;
207
+ age!: number;
208
+
209
+ // @ts-expect-error DELETE is not allowed
210
+ @Route({ method: 'DELETE', path: '/users/:id' })
211
+ async getUser(id: string): Promise<UserDto> {
212
+ return { id, email: this.email, age: this.age };
213
+ }
214
+
215
+ async createUser(input: CreateUserInput): Promise<UserDto> {
216
+ return { id: 'new-id', email: input.email, age: input.age };
217
+ }
218
+ }
219
+
220
+ class BadMethodTarget {
221
+ id!: string;
222
+ email!: string;
223
+ age!: number;
224
+
225
+ // @ts-expect-error route target requires Promise return
226
+ @Route({ method: 'GET', path: '/users/:id' })
227
+ getUser(id: string): UserDto {
228
+ return { id, email: this.email, age: this.age };
229
+ }
230
+
231
+ async createUser(input: CreateUserInput): Promise<UserDto> {
232
+ return { id: 'new-id', email: input.email, age: input.age };
233
+ }
234
+ }
235
+
236
+ class BadMethodKey implements UserControllerContract {
237
+ id!: string;
238
+ email!: string;
239
+ age!: number;
240
+
241
+ // @ts-expect-error decorator key does not match method name
242
+ @Route('createUser', { method: 'GET', path: '/users/:id' })
243
+ async getUser(id: string): Promise<UserDto> {
244
+ return { id, email: this.email, age: this.age };
245
+ }
246
+
247
+ async createUser(input: CreateUserInput): Promise<UserDto> {
248
+ return { id: 'new-id', email: input.email, age: input.age };
249
+ }
250
+ }
251
+
252
+ class GoodAccessor implements UserControllerContract {
253
+ id!: string;
254
+ age!: number;
255
+
256
+ @Cached('email', { ttlMs: 1000 })
257
+ accessor email = 'Ray';
258
+
259
+ async getUser(id: string): Promise<UserDto> {
260
+ return { id, email: this.email, age: this.age };
261
+ }
262
+
263
+ async createUser(input: CreateUserInput): Promise<UserDto> {
264
+ return { id: 'new-id', email: input.email, age: input.age };
265
+ }
266
+ }
267
+
268
+ class BadAccessorTarget implements UserControllerContract {
269
+ id!: string;
270
+ email!: string;
271
+ age!: number;
272
+
273
+ // @ts-expect-error cached target is string but count is number
274
+ @Cached({ ttlMs: 1000 })
275
+ accessor count = 1;
276
+
277
+ async getUser(id: string): Promise<UserDto> {
278
+ return { id, email: this.email, age: this.age };
279
+ }
280
+
281
+ async createUser(input: CreateUserInput): Promise<UserDto> {
282
+ return { id: 'new-id', email: input.email, age: input.age };
283
+ }
284
+ }
285
+
286
+ class BadAccessorMetadata implements UserControllerContract {
287
+ id!: string;
288
+ email!: string;
289
+ age!: number;
290
+
291
+ // @ts-expect-error ttlMs must be number
292
+ @Cached({ ttlMs: '1000' })
293
+ accessor label = 'Ray';
294
+
295
+ async getUser(id: string): Promise<UserDto> {
296
+ return { id, email: this.email, age: this.age };
297
+ }
298
+
299
+ async createUser(input: CreateUserInput): Promise<UserDto> {
300
+ return { id: 'new-id', email: input.email, age: input.age };
301
+ }
302
+ }