@navios/core 0.8.0 → 0.9.1
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/CHANGELOG.md +60 -0
- package/lib/{index-BDNl7j1G.d.cts → index-B2ulzZIr.d.cts} +65 -13
- package/lib/index-B2ulzZIr.d.cts.map +1 -0
- package/lib/{index-BoP0cWT6.d.mts → index-C8lUQePd.d.mts} +65 -13
- package/lib/index-C8lUQePd.d.mts.map +1 -0
- package/lib/index.cjs +5 -2
- package/lib/index.d.cts +2 -2
- package/lib/index.d.mts +2 -2
- package/lib/index.mjs +3 -3
- package/lib/legacy-compat/index.cjs +133 -1
- package/lib/legacy-compat/index.cjs.map +1 -1
- package/lib/legacy-compat/index.d.cts +219 -7
- package/lib/legacy-compat/index.d.cts.map +1 -1
- package/lib/legacy-compat/index.d.mts +219 -7
- package/lib/legacy-compat/index.d.mts.map +1 -1
- package/lib/legacy-compat/index.mjs +128 -2
- package/lib/legacy-compat/index.mjs.map +1 -1
- package/lib/{src-gBAChVRL.mjs → src-Baabu2R8.mjs} +17 -12
- package/lib/src-Baabu2R8.mjs.map +1 -0
- package/lib/{src-B6eISODM.cjs → src-Cu9OAnfp.cjs} +16 -11
- package/lib/src-Cu9OAnfp.cjs.map +1 -0
- package/lib/testing/index.cjs +346 -29
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +299 -63
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +299 -63
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +347 -31
- package/lib/testing/index.mjs.map +1 -1
- package/lib/{use-guards.decorator-CUww54Nt.mjs → use-guards.decorator-ChJVzYLW.mjs} +38 -9
- package/lib/use-guards.decorator-ChJVzYLW.mjs.map +1 -0
- package/lib/{use-guards.decorator-COR-9mZY.cjs → use-guards.decorator-EvI2m2DK.cjs} +56 -9
- package/lib/use-guards.decorator-EvI2m2DK.cjs.map +1 -0
- package/package.json +4 -4
- package/src/__tests__/controller-resolver.spec.mts +19 -13
- package/src/__tests__/testing-module.spec.mts +459 -0
- package/src/__tests__/unit-testing-module.spec.mts +424 -0
- package/src/attribute.factory.mts +19 -3
- package/src/decorators/controller.decorator.mts +19 -2
- package/src/decorators/module.decorator.mts +23 -5
- package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +114 -10
- package/src/legacy-compat/attribute.factory.mts +365 -0
- package/src/legacy-compat/context-compat.mts +2 -0
- package/src/legacy-compat/decorators/index.mts +1 -0
- package/src/legacy-compat/decorators/injectable.decorator.mts +41 -0
- package/src/legacy-compat/decorators/multipart.decorator.mts +4 -4
- package/src/legacy-compat/decorators/stream.decorator.mts +21 -14
- package/src/legacy-compat/index.mts +14 -3
- package/src/metadata/index.mts +1 -0
- package/src/metadata/navios-managed.metadata.mts +42 -0
- package/src/navios.application.mts +9 -9
- package/src/navios.environment.mts +3 -1
- package/src/navios.factory.mts +9 -27
- package/src/services/instance-resolver.service.mts +8 -7
- package/src/services/module-loader.service.mts +3 -2
- package/src/testing/index.mts +1 -0
- package/src/testing/testing-module.mts +255 -93
- package/src/testing/unit-testing-module.mts +298 -0
- package/lib/index-BDNl7j1G.d.cts.map +0 -1
- package/lib/index-BoP0cWT6.d.mts.map +0 -1
- package/lib/src-B6eISODM.cjs.map +0 -1
- package/lib/src-gBAChVRL.mjs.map +0 -1
- package/lib/use-guards.decorator-COR-9mZY.cjs.map +0 -1
- package/lib/use-guards.decorator-CUww54Nt.mjs.map +0 -1
|
@@ -13,10 +13,12 @@ import type {
|
|
|
13
13
|
} from '../index.mjs'
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
+
AttributeFactory,
|
|
16
17
|
Controller,
|
|
17
18
|
Endpoint,
|
|
18
19
|
Header,
|
|
19
20
|
HttpCode,
|
|
21
|
+
Injectable,
|
|
20
22
|
Module,
|
|
21
23
|
Multipart,
|
|
22
24
|
Stream,
|
|
@@ -138,9 +140,7 @@ describe('Legacy Decorators Type Safety', () => {
|
|
|
138
140
|
// - request.urlParams.userId: string | number
|
|
139
141
|
// - request.params.page: number
|
|
140
142
|
// - request.params.limit: number
|
|
141
|
-
expectTypeOf(request.urlParams.userId).toEqualTypeOf<
|
|
142
|
-
string | number
|
|
143
|
-
>()
|
|
143
|
+
expectTypeOf(request.urlParams.userId).toEqualTypeOf<string>()
|
|
144
144
|
expectTypeOf(request.params.page).toEqualTypeOf<number>()
|
|
145
145
|
expectTypeOf(request.params.limit).toEqualTypeOf<number>()
|
|
146
146
|
return {
|
|
@@ -261,18 +261,16 @@ describe('Legacy Decorators Type Safety', () => {
|
|
|
261
261
|
test('should enforce correct parameter type', () => {
|
|
262
262
|
@Controller()
|
|
263
263
|
class FileController {
|
|
264
|
+
// @ts-expect-error - legacy decorator is not typed
|
|
264
265
|
@Stream(downloadFileEndpoint)
|
|
265
266
|
async downloadFile(
|
|
266
267
|
request: StreamParams<typeof downloadFileEndpoint>,
|
|
267
|
-
reply: any,
|
|
268
268
|
): Promise<void> {
|
|
269
269
|
// TypeScript should infer:
|
|
270
270
|
// - request.urlParams.fileId: string | number
|
|
271
271
|
// - request.params.page: number
|
|
272
272
|
// - request.params.limit: number
|
|
273
|
-
expectTypeOf(request.urlParams.fileId).toEqualTypeOf<
|
|
274
|
-
string | number
|
|
275
|
-
>()
|
|
273
|
+
expectTypeOf(request.urlParams.fileId).toEqualTypeOf<string>()
|
|
276
274
|
expectTypeOf(request.params.page).toEqualTypeOf<number>()
|
|
277
275
|
expectTypeOf(request.params.limit).toEqualTypeOf<number>()
|
|
278
276
|
}
|
|
@@ -281,17 +279,20 @@ describe('Legacy Decorators Type Safety', () => {
|
|
|
281
279
|
expectTypeOf(FileController).toBeConstructibleWith()
|
|
282
280
|
})
|
|
283
281
|
|
|
284
|
-
test('should
|
|
282
|
+
test('should work without reply parameter (Bun compatibility)', () => {
|
|
285
283
|
@Controller()
|
|
286
284
|
class FileController {
|
|
287
|
-
// @ts-expect-error -
|
|
285
|
+
// @ts-expect-error - legacy decorator is not typed
|
|
288
286
|
@Stream(downloadFileEndpoint)
|
|
289
287
|
async downloadFile(
|
|
290
288
|
request: StreamParams<typeof downloadFileEndpoint>,
|
|
291
289
|
): Promise<void> {
|
|
292
|
-
// Stream methods
|
|
290
|
+
// Stream methods can work without reply parameter (for Bun)
|
|
291
|
+
expectTypeOf(request.urlParams.fileId).toEqualTypeOf<string>()
|
|
293
292
|
}
|
|
294
293
|
}
|
|
294
|
+
|
|
295
|
+
expectTypeOf(FileController).toBeConstructibleWith()
|
|
295
296
|
})
|
|
296
297
|
})
|
|
297
298
|
|
|
@@ -376,6 +377,109 @@ describe('Legacy Decorators Type Safety', () => {
|
|
|
376
377
|
})
|
|
377
378
|
})
|
|
378
379
|
|
|
380
|
+
describe('Injectable decorator', () => {
|
|
381
|
+
test('should work with services', () => {
|
|
382
|
+
@Injectable()
|
|
383
|
+
class UserService {
|
|
384
|
+
getUser(id: string) {
|
|
385
|
+
return { id, name: 'John' }
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
expectTypeOf(UserService).toBeConstructibleWith()
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
test('should work with controller dependencies', () => {
|
|
393
|
+
@Injectable()
|
|
394
|
+
class UserService {
|
|
395
|
+
getUser(id: string) {
|
|
396
|
+
return { id, name: 'John' }
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@Controller()
|
|
401
|
+
class UserController {
|
|
402
|
+
constructor(private readonly userService: UserService) {}
|
|
403
|
+
|
|
404
|
+
@Endpoint(getUserEndpoint)
|
|
405
|
+
async getUser(
|
|
406
|
+
request: EndpointParams<typeof getUserEndpoint>,
|
|
407
|
+
): EndpointResult<typeof getUserEndpoint> {
|
|
408
|
+
return this.userService.getUser(request.urlParams.userId.toString())
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
expectTypeOf(UserController).toBeConstructibleWith(
|
|
413
|
+
{} as InstanceType<typeof UserService>,
|
|
414
|
+
)
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe('AttributeFactory', () => {
|
|
419
|
+
test('should create simple attribute for controllers', () => {
|
|
420
|
+
const Public = AttributeFactory.createAttribute(Symbol.for('Public'))
|
|
421
|
+
|
|
422
|
+
// Note: In legacy decorators, decorators are applied bottom-to-top
|
|
423
|
+
// So @Controller() must be at the bottom to run first
|
|
424
|
+
@Public()
|
|
425
|
+
@Controller()
|
|
426
|
+
class PublicController {
|
|
427
|
+
@Endpoint(getUserEndpoint)
|
|
428
|
+
async getUser(
|
|
429
|
+
request: EndpointParams<typeof getUserEndpoint>,
|
|
430
|
+
): EndpointResult<typeof getUserEndpoint> {
|
|
431
|
+
return { id: '1', name: 'John' }
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
expectTypeOf(PublicController).toBeConstructibleWith()
|
|
436
|
+
expectTypeOf(Public.token).toEqualTypeOf<symbol>()
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test('should create attribute with schema for endpoints', () => {
|
|
440
|
+
const RateLimit = AttributeFactory.createAttribute(
|
|
441
|
+
Symbol.for('RateLimit'),
|
|
442
|
+
z.object({ requests: z.number(), window: z.number() }),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
@Controller()
|
|
446
|
+
class RateLimitedController {
|
|
447
|
+
// Note: For method decorators, @Endpoint must be at the bottom
|
|
448
|
+
// @ts-expect-error - legacy decorator is not typed correctly
|
|
449
|
+
@RateLimit({ requests: 100, window: 60000 })
|
|
450
|
+
@Endpoint(getUserEndpoint)
|
|
451
|
+
async getUser(
|
|
452
|
+
request: EndpointParams<typeof getUserEndpoint>,
|
|
453
|
+
): EndpointResult<typeof getUserEndpoint> {
|
|
454
|
+
return { id: '1', name: 'John' }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
expectTypeOf(RateLimitedController).toBeConstructibleWith()
|
|
459
|
+
expectTypeOf(RateLimit.token).toEqualTypeOf<symbol>()
|
|
460
|
+
expectTypeOf(RateLimit.schema).toBeObject()
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
test('should create attribute for modules', () => {
|
|
464
|
+
const ApiVersion = AttributeFactory.createAttribute(
|
|
465
|
+
Symbol.for('ApiVersion'),
|
|
466
|
+
z.string(),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
@Controller()
|
|
470
|
+
class VersionedController {}
|
|
471
|
+
|
|
472
|
+
// Note: In legacy decorators, @Module must be at the bottom
|
|
473
|
+
@ApiVersion('v2')
|
|
474
|
+
@Module({
|
|
475
|
+
controllers: [VersionedController],
|
|
476
|
+
})
|
|
477
|
+
class VersionedModule {}
|
|
478
|
+
|
|
479
|
+
expectTypeOf(VersionedModule).toBeConstructibleWith()
|
|
480
|
+
})
|
|
481
|
+
})
|
|
482
|
+
|
|
379
483
|
describe('Integration test - full controller', () => {
|
|
380
484
|
test('should work with all decorators together', () => {
|
|
381
485
|
@Controller({
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/di'
|
|
2
|
+
import type { z, ZodType } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ControllerMetadata,
|
|
6
|
+
HandlerMetadata,
|
|
7
|
+
ModuleMetadata,
|
|
8
|
+
} from '../metadata/index.mjs'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
getControllerMetadata,
|
|
12
|
+
getEndpointMetadata,
|
|
13
|
+
getModuleMetadata,
|
|
14
|
+
hasControllerMetadata,
|
|
15
|
+
hasModuleMetadata,
|
|
16
|
+
} from '../metadata/index.mjs'
|
|
17
|
+
import {
|
|
18
|
+
getManagedMetadata,
|
|
19
|
+
hasManagedMetadata,
|
|
20
|
+
} from '../metadata/navios-managed.metadata.mjs'
|
|
21
|
+
import { createClassContext, createMethodContext } from './context-compat.mjs'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Type for a legacy class attribute decorator without a value.
|
|
25
|
+
*
|
|
26
|
+
* Attributes are custom metadata decorators that can be applied to modules,
|
|
27
|
+
* controllers, and endpoints.
|
|
28
|
+
*/
|
|
29
|
+
export type LegacyClassAttribute = (() => <T extends ClassType>(
|
|
30
|
+
target: T,
|
|
31
|
+
) => T) &
|
|
32
|
+
(() => <T extends object>(
|
|
33
|
+
target: T,
|
|
34
|
+
propertyKey: string | symbol,
|
|
35
|
+
descriptor: PropertyDescriptor,
|
|
36
|
+
) => PropertyDescriptor) & {
|
|
37
|
+
token: symbol
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Type for a legacy class attribute decorator with a validated value.
|
|
42
|
+
*
|
|
43
|
+
* @typeParam S - The Zod schema type for validation
|
|
44
|
+
*/
|
|
45
|
+
export type LegacyClassSchemaAttribute<S extends ZodType> = ((
|
|
46
|
+
value: z.input<S>,
|
|
47
|
+
) => <T extends ClassType>(target: T) => T) &
|
|
48
|
+
((
|
|
49
|
+
value: z.input<S>,
|
|
50
|
+
) => <T extends object>(
|
|
51
|
+
target: T,
|
|
52
|
+
propertyKey: string | symbol,
|
|
53
|
+
descriptor: PropertyDescriptor,
|
|
54
|
+
) => PropertyDescriptor) & {
|
|
55
|
+
token: symbol
|
|
56
|
+
schema: ZodType
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Legacy-compatible factory for creating custom attribute decorators.
|
|
61
|
+
*
|
|
62
|
+
* Works with TypeScript experimental decorators (legacy API).
|
|
63
|
+
*
|
|
64
|
+
* Attributes allow you to add custom metadata to modules, controllers, and endpoints.
|
|
65
|
+
* This is useful for cross-cutting concerns like rate limiting, caching, API versioning, etc.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Create a simple boolean attribute
|
|
70
|
+
* const Public = LegacyAttributeFactory.createAttribute(Symbol.for('Public'))
|
|
71
|
+
*
|
|
72
|
+
* // Use it as a decorator
|
|
73
|
+
* @Controller()
|
|
74
|
+
* @Public()
|
|
75
|
+
* export class PublicController { }
|
|
76
|
+
*
|
|
77
|
+
* // Check if attribute exists
|
|
78
|
+
* if (LegacyAttributeFactory.has(Public, controllerMetadata)) {
|
|
79
|
+
* // Skip authentication
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Create an attribute with a validated value
|
|
86
|
+
* const RateLimit = LegacyAttributeFactory.createAttribute(
|
|
87
|
+
* Symbol.for('RateLimit'),
|
|
88
|
+
* z.object({ requests: z.number(), window: z.number() })
|
|
89
|
+
* )
|
|
90
|
+
*
|
|
91
|
+
* // Use it with a value
|
|
92
|
+
* @Endpoint(apiEndpoint)
|
|
93
|
+
* @RateLimit({ requests: 100, window: 60000 })
|
|
94
|
+
* async handler() { }
|
|
95
|
+
*
|
|
96
|
+
* // Get the value
|
|
97
|
+
* const limit = LegacyAttributeFactory.get(RateLimit, endpointMetadata)
|
|
98
|
+
* // limit is typed as { requests: number, window: number } | null
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export class LegacyAttributeFactory {
|
|
102
|
+
/**
|
|
103
|
+
* Creates a simple attribute decorator without a value.
|
|
104
|
+
*
|
|
105
|
+
* @param token - A unique symbol to identify this attribute
|
|
106
|
+
* @returns A decorator function that can be applied to classes or methods
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const Public = LegacyAttributeFactory.createAttribute(Symbol.for('Public'))
|
|
111
|
+
*
|
|
112
|
+
* @Public()
|
|
113
|
+
* @Controller()
|
|
114
|
+
* export class PublicController { }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
static createAttribute(token: symbol): LegacyClassAttribute
|
|
118
|
+
/**
|
|
119
|
+
* Creates an attribute decorator with a validated value.
|
|
120
|
+
*
|
|
121
|
+
* @param token - A unique symbol to identify this attribute
|
|
122
|
+
* @param schema - A Zod schema to validate the attribute value
|
|
123
|
+
* @returns A decorator function that accepts a value and can be applied to classes or methods
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const RateLimit = LegacyAttributeFactory.createAttribute(
|
|
128
|
+
* Symbol.for('RateLimit'),
|
|
129
|
+
* z.object({ requests: z.number(), window: z.number() })
|
|
130
|
+
* )
|
|
131
|
+
*
|
|
132
|
+
* @RateLimit({ requests: 100, window: 60000 })
|
|
133
|
+
* @Endpoint(apiEndpoint)
|
|
134
|
+
* async handler() { }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
static createAttribute<T extends ZodType>(
|
|
138
|
+
token: symbol,
|
|
139
|
+
schema: T,
|
|
140
|
+
): LegacyClassSchemaAttribute<T>
|
|
141
|
+
|
|
142
|
+
static createAttribute(token: symbol, schema?: ZodType) {
|
|
143
|
+
const res = (value?: unknown) => {
|
|
144
|
+
// Return a decorator that can handle both class and method targets
|
|
145
|
+
function decorator(target: any): any
|
|
146
|
+
function decorator(
|
|
147
|
+
target: any,
|
|
148
|
+
propertyKey: string | symbol,
|
|
149
|
+
descriptor: PropertyDescriptor,
|
|
150
|
+
): PropertyDescriptor
|
|
151
|
+
function decorator(
|
|
152
|
+
target: any,
|
|
153
|
+
propertyKey?: string | symbol,
|
|
154
|
+
descriptor?: PropertyDescriptor,
|
|
155
|
+
): any {
|
|
156
|
+
const isMethodDecorator =
|
|
157
|
+
propertyKey !== undefined && descriptor !== undefined
|
|
158
|
+
|
|
159
|
+
if (isMethodDecorator) {
|
|
160
|
+
// Method decorator - apply to endpoint
|
|
161
|
+
const context = createMethodContext(target, propertyKey, descriptor)
|
|
162
|
+
const metadata = getEndpointMetadata(descriptor.value, context)
|
|
163
|
+
|
|
164
|
+
if (schema) {
|
|
165
|
+
const validatedValue = schema.safeParse(value)
|
|
166
|
+
if (!validatedValue.success) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`[Navios] Invalid value for attribute ${token.toString()}: ${validatedValue.error}`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
metadata.customAttributes.set(token, validatedValue.data)
|
|
172
|
+
} else {
|
|
173
|
+
metadata.customAttributes.set(token, true)
|
|
174
|
+
}
|
|
175
|
+
return descriptor
|
|
176
|
+
} else {
|
|
177
|
+
// Class decorator
|
|
178
|
+
const isController = hasControllerMetadata(target as ClassType)
|
|
179
|
+
const isModule = hasModuleMetadata(target as ClassType)
|
|
180
|
+
const isManaged = hasManagedMetadata(target as ClassType)
|
|
181
|
+
|
|
182
|
+
if (!isController && !isModule && !isManaged) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
'[Navios] Attribute can only be applied to classes with @Controller, @Module, or other Navios-managed decorators',
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const context = createClassContext(target)
|
|
189
|
+
const metadata = isController
|
|
190
|
+
? getControllerMetadata(target, context)
|
|
191
|
+
: isModule
|
|
192
|
+
? getModuleMetadata(target, context)
|
|
193
|
+
: isManaged
|
|
194
|
+
? getManagedMetadata(target)!
|
|
195
|
+
: null
|
|
196
|
+
|
|
197
|
+
if (!metadata) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
'[Navios] Could not determine metadata for attribute target',
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (schema) {
|
|
204
|
+
const validatedValue = schema.safeParse(value)
|
|
205
|
+
if (!validatedValue.success) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`[Navios] Invalid value for attribute ${token.toString()}: ${validatedValue.error}`,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
metadata.customAttributes.set(token, validatedValue.data)
|
|
211
|
+
} else {
|
|
212
|
+
metadata.customAttributes.set(token, true)
|
|
213
|
+
}
|
|
214
|
+
return target
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return decorator
|
|
218
|
+
}
|
|
219
|
+
res.token = token
|
|
220
|
+
if (schema) {
|
|
221
|
+
res.schema = schema
|
|
222
|
+
}
|
|
223
|
+
return res
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Gets the value of an attribute from metadata.
|
|
228
|
+
*
|
|
229
|
+
* Returns `null` if the attribute is not present.
|
|
230
|
+
* For simple attributes (without values), returns `true` if present.
|
|
231
|
+
*
|
|
232
|
+
* @param attribute - The attribute decorator
|
|
233
|
+
* @param target - The metadata object (module, controller, or handler)
|
|
234
|
+
* @returns The attribute value, `true` for simple attributes, or `null` if not found
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* const isPublic = LegacyAttributeFactory.get(Public, controllerMetadata)
|
|
239
|
+
* // isPublic is true | null
|
|
240
|
+
*
|
|
241
|
+
* const rateLimit = LegacyAttributeFactory.get(RateLimit, endpointMetadata)
|
|
242
|
+
* // rateLimit is { requests: number, window: number } | null
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
static get(
|
|
246
|
+
attribute: LegacyClassAttribute,
|
|
247
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
248
|
+
): true | null
|
|
249
|
+
static get<T extends ZodType>(
|
|
250
|
+
attribute: LegacyClassSchemaAttribute<T>,
|
|
251
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
252
|
+
): z.output<T> | null
|
|
253
|
+
static get(
|
|
254
|
+
attribute: LegacyClassAttribute | LegacyClassSchemaAttribute<any>,
|
|
255
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
256
|
+
) {
|
|
257
|
+
return target.customAttributes.get(attribute.token) ?? null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Gets all values of an attribute from metadata (useful when an attribute can appear multiple times).
|
|
262
|
+
*
|
|
263
|
+
* Returns `null` if the attribute is not present.
|
|
264
|
+
*
|
|
265
|
+
* @param attribute - The attribute decorator
|
|
266
|
+
* @param target - The metadata object (module, controller, or handler)
|
|
267
|
+
* @returns An array of attribute values, or `null` if not found
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* const tags = LegacyAttributeFactory.getAll(Tag, endpointMetadata)
|
|
272
|
+
* // tags is string[] | null
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
static getAll(
|
|
276
|
+
attribute: LegacyClassAttribute,
|
|
277
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
278
|
+
): Array<true> | null
|
|
279
|
+
static getAll<T extends ZodType>(
|
|
280
|
+
attribute: LegacyClassSchemaAttribute<T>,
|
|
281
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
282
|
+
): Array<z.output<T>> | null
|
|
283
|
+
static getAll(
|
|
284
|
+
attribute: LegacyClassAttribute | LegacyClassSchemaAttribute<any>,
|
|
285
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
286
|
+
) {
|
|
287
|
+
const values = Array.from(target.customAttributes.entries())
|
|
288
|
+
.filter(([key]) => key === attribute.token)
|
|
289
|
+
.map(([, value]) => value)
|
|
290
|
+
return values.length > 0 ? values : null
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Gets the last value of an attribute from an array of metadata objects.
|
|
295
|
+
*
|
|
296
|
+
* Searches from the end of the array backwards, useful for finding the most
|
|
297
|
+
* specific attribute value (e.g., endpoint-level overrides module-level).
|
|
298
|
+
*
|
|
299
|
+
* @param attribute - The attribute decorator
|
|
300
|
+
* @param target - An array of metadata objects (typically [module, controller, handler])
|
|
301
|
+
* @returns The last attribute value found, or `null` if not found
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* // Check attribute hierarchy: endpoint -> controller -> module
|
|
306
|
+
* const rateLimit = LegacyAttributeFactory.getLast(RateLimit, [
|
|
307
|
+
* moduleMetadata,
|
|
308
|
+
* controllerMetadata,
|
|
309
|
+
* endpointMetadata
|
|
310
|
+
* ])
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
static getLast(
|
|
314
|
+
attribute: LegacyClassAttribute,
|
|
315
|
+
target: (ModuleMetadata | ControllerMetadata | HandlerMetadata<any>)[],
|
|
316
|
+
): true | null
|
|
317
|
+
static getLast<T extends ZodType>(
|
|
318
|
+
attribute: LegacyClassSchemaAttribute<T>,
|
|
319
|
+
target: (ModuleMetadata | ControllerMetadata | HandlerMetadata<any>)[],
|
|
320
|
+
): z.output<T> | null
|
|
321
|
+
static getLast(
|
|
322
|
+
attribute: LegacyClassAttribute | LegacyClassSchemaAttribute<any>,
|
|
323
|
+
target: (ModuleMetadata | ControllerMetadata | HandlerMetadata<any>)[],
|
|
324
|
+
) {
|
|
325
|
+
for (let i = target.length - 1; i >= 0; i--) {
|
|
326
|
+
const value = target[i].customAttributes.get(attribute.token)
|
|
327
|
+
if (value) {
|
|
328
|
+
return value
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return null
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Checks if an attribute is present on the metadata object.
|
|
336
|
+
*
|
|
337
|
+
* @param attribute - The attribute decorator
|
|
338
|
+
* @param target - The metadata object (module, controller, or handler)
|
|
339
|
+
* @returns `true` if the attribute is present, `false` otherwise
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* if (LegacyAttributeFactory.has(Public, controllerMetadata)) {
|
|
344
|
+
* // Skip authentication
|
|
345
|
+
* }
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
static has(
|
|
349
|
+
attribute: LegacyClassAttribute,
|
|
350
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
351
|
+
): boolean
|
|
352
|
+
static has<T extends ZodType>(
|
|
353
|
+
attribute: LegacyClassSchemaAttribute<T>,
|
|
354
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
355
|
+
): boolean
|
|
356
|
+
static has(
|
|
357
|
+
attribute: LegacyClassAttribute | LegacyClassSchemaAttribute<any>,
|
|
358
|
+
target: ModuleMetadata | ControllerMetadata | HandlerMetadata<any>,
|
|
359
|
+
) {
|
|
360
|
+
return target.customAttributes.has(attribute.token)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Re-export as AttributeFactory for convenience
|
|
365
|
+
export { LegacyAttributeFactory as AttributeFactory }
|
|
@@ -30,6 +30,7 @@ function getConstructor(prototype: any): ClassType | null {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Creates a mock ClassDecoratorContext for legacy class decorators.
|
|
33
|
+
* @internal
|
|
33
34
|
*/
|
|
34
35
|
export function createClassContext(target: ClassType): ClassDecoratorContext {
|
|
35
36
|
// Get or create metadata storage for this class
|
|
@@ -53,6 +54,7 @@ export function createClassContext(target: ClassType): ClassDecoratorContext {
|
|
|
53
54
|
*
|
|
54
55
|
* Note: Method decorators need to share metadata with the class context
|
|
55
56
|
* because endpoint metadata is stored at the class level.
|
|
57
|
+
* @internal
|
|
56
58
|
*/
|
|
57
59
|
export function createMethodContext(
|
|
58
60
|
target: any,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Injectable as OriginalInjectable,
|
|
5
|
+
type InjectableOptions,
|
|
6
|
+
} from '@navios/di'
|
|
7
|
+
|
|
8
|
+
import { createClassContext } from '../context-compat.mjs'
|
|
9
|
+
|
|
10
|
+
export type { InjectableOptions }
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Legacy-compatible Injectable decorator.
|
|
14
|
+
*
|
|
15
|
+
* Works with TypeScript experimental decorators (legacy API).
|
|
16
|
+
*
|
|
17
|
+
* @param options - Injectable configuration options
|
|
18
|
+
* @returns A class decorator compatible with legacy decorator API
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* @Injectable()
|
|
23
|
+
* export class UserService {
|
|
24
|
+
* getUser(id: string) {
|
|
25
|
+
* return { id, name: 'John' }
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function Injectable(options: InjectableOptions = {}) {
|
|
31
|
+
return function (target: ClassType) {
|
|
32
|
+
const context = createClassContext(target)
|
|
33
|
+
// Use the no-args overload when options is empty, otherwise pass options
|
|
34
|
+
const originalDecorator =
|
|
35
|
+
Object.keys(options).length === 0
|
|
36
|
+
? OriginalInjectable()
|
|
37
|
+
: // @ts-expect-error - InjectableOptions is a union type, we let runtime handle it
|
|
38
|
+
OriginalInjectable(options)
|
|
39
|
+
return originalDecorator(target, context)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -22,11 +22,11 @@ type MultipartMethodDescriptor<
|
|
|
22
22
|
(
|
|
23
23
|
params: QuerySchema extends ZodObject
|
|
24
24
|
? RequestSchema extends ZodType
|
|
25
|
-
? EndpointFunctionArgs<Url, QuerySchema, RequestSchema>
|
|
26
|
-
: EndpointFunctionArgs<Url, QuerySchema, undefined>
|
|
25
|
+
? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
|
|
26
|
+
: EndpointFunctionArgs<Url, QuerySchema, undefined, true>
|
|
27
27
|
: RequestSchema extends ZodType
|
|
28
|
-
? EndpointFunctionArgs<Url, undefined, RequestSchema>
|
|
29
|
-
: EndpointFunctionArgs<Url, undefined, undefined>,
|
|
28
|
+
? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
|
|
29
|
+
: EndpointFunctionArgs<Url, undefined, undefined, true>,
|
|
30
30
|
) => Promise<z.input<ResponseSchema>>
|
|
31
31
|
>
|
|
32
32
|
|
|
@@ -8,27 +8,34 @@ import type { ZodObject, ZodType } from 'zod/v4'
|
|
|
8
8
|
import { Stream as OriginalStream } from '../../decorators/stream.decorator.mjs'
|
|
9
9
|
import { createMethodContext } from '../context-compat.mjs'
|
|
10
10
|
|
|
11
|
+
type StreamParams<
|
|
12
|
+
Url extends string,
|
|
13
|
+
QuerySchema,
|
|
14
|
+
RequestSchema,
|
|
15
|
+
> = QuerySchema extends ZodObject
|
|
16
|
+
? RequestSchema extends ZodType
|
|
17
|
+
? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
|
|
18
|
+
: EndpointFunctionArgs<Url, QuerySchema, undefined, true>
|
|
19
|
+
: RequestSchema extends ZodType
|
|
20
|
+
? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
|
|
21
|
+
: EndpointFunctionArgs<Url, undefined, undefined, true>
|
|
22
|
+
|
|
11
23
|
/**
|
|
12
24
|
* Type helper to constrain a PropertyDescriptor's value to match a stream endpoint signature.
|
|
13
|
-
*
|
|
14
|
-
* but may not be preserved perfectly when decorators are stacked.
|
|
25
|
+
* Supports both with and without reply parameter (Bun doesn't use reply parameter).
|
|
15
26
|
*/
|
|
16
27
|
type StreamMethodDescriptor<
|
|
17
28
|
Url extends string,
|
|
18
29
|
QuerySchema,
|
|
19
30
|
RequestSchema,
|
|
20
|
-
> =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
: EndpointFunctionArgs<Url, undefined, undefined>,
|
|
29
|
-
reply: any,
|
|
30
|
-
) => Promise<void>
|
|
31
|
-
>
|
|
31
|
+
> =
|
|
32
|
+
| TypedPropertyDescriptor<
|
|
33
|
+
(params: StreamParams<Url, QuerySchema, RequestSchema>, reply: any) => any
|
|
34
|
+
>
|
|
35
|
+
| TypedPropertyDescriptor<
|
|
36
|
+
(params: StreamParams<Url, QuerySchema, RequestSchema>) => any
|
|
37
|
+
>
|
|
38
|
+
| TypedPropertyDescriptor<() => any>
|
|
32
39
|
|
|
33
40
|
/**
|
|
34
41
|
* Legacy-compatible Stream decorator.
|