@webpieces/http-api 0.2.91 → 0.2.93
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/package.json +2 -2
- package/src/decorators.d.ts +67 -57
- package/src/decorators.js +132 -115
- package/src/decorators.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +11 -10
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-api",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.93",
|
|
4
4
|
"description": "HTTP API decorators for defining REST APIs",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/core-util": "0.2.
|
|
25
|
+
"@webpieces/core-util": "0.2.93"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/src/decorators.d.ts
CHANGED
|
@@ -4,94 +4,104 @@ import 'reflect-metadata';
|
|
|
4
4
|
* These keys are used by both server-side (routing) and client-side (client generation).
|
|
5
5
|
*/
|
|
6
6
|
export declare const METADATA_KEYS: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
PATH: string;
|
|
7
|
+
API_PATH: string;
|
|
8
|
+
ENDPOINTS: string;
|
|
9
|
+
AUTH_META: string;
|
|
11
10
|
};
|
|
12
11
|
/**
|
|
13
|
-
* Route metadata stored
|
|
14
|
-
* Used by
|
|
12
|
+
* Route metadata stored per-method at runtime.
|
|
13
|
+
* Used internally by http-routing and http-client as the runtime representation
|
|
14
|
+
* of a route. Constructed from @ApiPath + @Endpoint metadata by createApiClient
|
|
15
|
+
* and ApiRoutingFactory.
|
|
15
16
|
*/
|
|
16
17
|
export declare class RouteMetadata {
|
|
17
18
|
httpMethod: string;
|
|
18
19
|
path: string;
|
|
19
20
|
methodName: string;
|
|
20
|
-
parameterTypes?: any[];
|
|
21
21
|
controllerClassName?: string;
|
|
22
|
-
|
|
22
|
+
authMeta?: AuthMeta;
|
|
23
|
+
constructor(httpMethod: string, path: string, methodName: string, controllerClassName?: string, authMeta?: AuthMeta);
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
-
* Similar to Java's JAX-RS interface pattern.
|
|
26
|
+
* Auth metadata attached to a class or method via @Authentication().
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
* -
|
|
30
|
-
* -
|
|
28
|
+
* - authenticated=false → public endpoint (no auth check)
|
|
29
|
+
* - authenticated=true, no roles → requires authentication
|
|
30
|
+
* - authenticated=true, roles=['admin'] → requires authentication + specific roles
|
|
31
|
+
* - authenticated=false + roles → INVALID (caught at decorator time)
|
|
32
|
+
*/
|
|
33
|
+
export declare class AuthMeta {
|
|
34
|
+
authenticated: boolean;
|
|
35
|
+
roles: string[];
|
|
36
|
+
constructor(authenticated: boolean, roles?: string[]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @ApiPath(basePath) - Class decorator that marks a class as an API definition
|
|
40
|
+
* and sets the base path for all endpoints.
|
|
31
41
|
*
|
|
32
42
|
* Usage:
|
|
33
43
|
* ```typescript
|
|
34
|
-
* @
|
|
44
|
+
* @Authentication({authenticated: true})
|
|
45
|
+
* @ApiPath('/api/save')
|
|
35
46
|
* abstract class SaveApiPrototype {
|
|
36
|
-
* @
|
|
37
|
-
*
|
|
38
|
-
* save(request: SaveRequest): Promise<SaveResponse> {
|
|
39
|
-
* throw new Error('Must be implemented');
|
|
40
|
-
* }
|
|
47
|
+
* @Endpoint('/item')
|
|
48
|
+
* save(request: SaveRequest): Promise<SaveResponse> { ... }
|
|
41
49
|
* }
|
|
42
50
|
* ```
|
|
43
51
|
*/
|
|
44
|
-
export declare function
|
|
52
|
+
export declare function ApiPath(basePath: string): ClassDecorator;
|
|
45
53
|
/**
|
|
46
|
-
* @
|
|
47
|
-
*
|
|
54
|
+
* @Endpoint(path) - Method decorator that registers a POST endpoint at the given path.
|
|
55
|
+
*
|
|
56
|
+
* All endpoints are POST-only (matching gRPC/thrift style).
|
|
57
|
+
*
|
|
58
|
+
* Usage:
|
|
59
|
+
* ```typescript
|
|
60
|
+
* @Endpoint('/item')
|
|
61
|
+
* save(request: SaveRequest): Promise<SaveResponse> { ... }
|
|
62
|
+
* ```
|
|
48
63
|
*/
|
|
49
|
-
export declare function
|
|
64
|
+
export declare function Endpoint(path: string): MethodDecorator;
|
|
50
65
|
/**
|
|
51
|
-
*
|
|
52
|
-
* Usage: @Post()
|
|
66
|
+
* Authentication config passed to @Authentication() decorator.
|
|
53
67
|
*/
|
|
54
|
-
export declare
|
|
68
|
+
export declare class AuthenticationConfig {
|
|
69
|
+
authenticated: boolean;
|
|
70
|
+
roles?: string[];
|
|
71
|
+
constructor(authenticated: boolean, roles?: string[]);
|
|
72
|
+
}
|
|
55
73
|
/**
|
|
56
|
-
* @
|
|
57
|
-
*
|
|
74
|
+
* @Authentication(config) - Class or method decorator for auth requirements.
|
|
75
|
+
*
|
|
76
|
+
* Single decorator replaces @Public/@Authenticated/@Roles:
|
|
77
|
+
* - @Authentication({authenticated: false}) → public, no auth check
|
|
78
|
+
* - @Authentication({authenticated: true}) → requires authentication
|
|
79
|
+
* - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles
|
|
80
|
+
*
|
|
81
|
+
* Class-level is required. Methods can override class-level.
|
|
82
|
+
* Throws if authenticated=false but roles are specified (contradictory).
|
|
58
83
|
*/
|
|
59
|
-
export declare function
|
|
84
|
+
export declare function Authentication(config: AuthenticationConfig): ClassDecorator & MethodDecorator;
|
|
60
85
|
/**
|
|
61
|
-
*
|
|
62
|
-
* Usage: @Delete()
|
|
86
|
+
* Get the base path from @ApiPath decorator.
|
|
63
87
|
*/
|
|
64
|
-
export declare function
|
|
88
|
+
export declare function getApiPath(apiClass: Function): string | undefined;
|
|
65
89
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
90
|
+
* Get all endpoints from @Endpoint decorators.
|
|
91
|
+
* Returns a record of methodName -> endpoint path.
|
|
68
92
|
*/
|
|
69
|
-
export declare function
|
|
93
|
+
export declare function getEndpoints(apiClass: Function): Record<string, string> | undefined;
|
|
70
94
|
/**
|
|
71
|
-
*
|
|
72
|
-
* Similar to JAX-RS @Path annotation.
|
|
73
|
-
*
|
|
74
|
-
* This decorator is used by:
|
|
75
|
-
* - Server: To register routes at the specified path
|
|
76
|
-
* - Client: To make HTTP requests to the specified path
|
|
77
|
-
*
|
|
78
|
-
* Usage:
|
|
79
|
-
* ```typescript
|
|
80
|
-
* @Post()
|
|
81
|
-
* @Path('/search/item')
|
|
82
|
-
* save(request: SaveRequest): Promise<SaveResponse> {
|
|
83
|
-
* throw new Error('Must be implemented');
|
|
84
|
-
* }
|
|
85
|
-
* ```
|
|
95
|
+
* Check if a class has @ApiPath decorator.
|
|
86
96
|
*/
|
|
87
|
-
export declare function
|
|
97
|
+
export declare function isApiPath(apiClass: Function): boolean;
|
|
88
98
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
99
|
+
* Get auth metadata for a specific method, falling back to class-level auth.
|
|
100
|
+
* Method-level auth takes precedence over class-level auth.
|
|
91
101
|
*/
|
|
92
|
-
export declare function
|
|
102
|
+
export declare function getAuthMeta(apiClass: Function, methodName?: string): AuthMeta | undefined;
|
|
93
103
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
104
|
+
* Validate that a class/method doesn't have conflicting auth decorators.
|
|
105
|
+
* @throws Error if multiple @Authentication decorators are found on the same target.
|
|
96
106
|
*/
|
|
97
|
-
export declare function
|
|
107
|
+
export declare function validateNoConflictingDecorators(apiClass: Function, methodName: string | undefined): void;
|
package/src/decorators.js
CHANGED
|
@@ -1,175 +1,192 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RouteMetadata = exports.METADATA_KEYS = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
12
|
-
exports.isApiInterface = isApiInterface;
|
|
3
|
+
exports.AuthenticationConfig = exports.AuthMeta = exports.RouteMetadata = exports.METADATA_KEYS = void 0;
|
|
4
|
+
exports.ApiPath = ApiPath;
|
|
5
|
+
exports.Endpoint = Endpoint;
|
|
6
|
+
exports.Authentication = Authentication;
|
|
7
|
+
exports.getApiPath = getApiPath;
|
|
8
|
+
exports.getEndpoints = getEndpoints;
|
|
9
|
+
exports.isApiPath = isApiPath;
|
|
10
|
+
exports.getAuthMeta = getAuthMeta;
|
|
11
|
+
exports.validateNoConflictingDecorators = validateNoConflictingDecorators;
|
|
13
12
|
require("reflect-metadata");
|
|
14
13
|
/**
|
|
15
14
|
* Metadata keys for storing API routing information.
|
|
16
15
|
* These keys are used by both server-side (routing) and client-side (client generation).
|
|
17
16
|
*/
|
|
18
17
|
exports.METADATA_KEYS = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
PATH: 'webpieces:path',
|
|
18
|
+
API_PATH: 'webpieces:api-path',
|
|
19
|
+
ENDPOINTS: 'webpieces:endpoints',
|
|
20
|
+
AUTH_META: 'webpieces:auth-meta',
|
|
23
21
|
};
|
|
24
22
|
/**
|
|
25
|
-
* Route metadata stored
|
|
26
|
-
* Used by
|
|
23
|
+
* Route metadata stored per-method at runtime.
|
|
24
|
+
* Used internally by http-routing and http-client as the runtime representation
|
|
25
|
+
* of a route. Constructed from @ApiPath + @Endpoint metadata by createApiClient
|
|
26
|
+
* and ApiRoutingFactory.
|
|
27
27
|
*/
|
|
28
28
|
class RouteMetadata {
|
|
29
|
-
constructor(httpMethod, path, methodName,
|
|
29
|
+
constructor(httpMethod, path, methodName, controllerClassName, authMeta) {
|
|
30
30
|
this.httpMethod = httpMethod;
|
|
31
31
|
this.path = path;
|
|
32
32
|
this.methodName = methodName;
|
|
33
|
-
this.parameterTypes = parameterTypes;
|
|
34
33
|
this.controllerClassName = controllerClassName;
|
|
34
|
+
this.authMeta = authMeta;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
exports.RouteMetadata = RouteMetadata;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
* Similar to Java's JAX-RS interface pattern.
|
|
39
|
+
* Auth metadata attached to a class or method via @Authentication().
|
|
41
40
|
*
|
|
42
|
-
*
|
|
43
|
-
* -
|
|
44
|
-
* -
|
|
41
|
+
* - authenticated=false → public endpoint (no auth check)
|
|
42
|
+
* - authenticated=true, no roles → requires authentication
|
|
43
|
+
* - authenticated=true, roles=['admin'] → requires authentication + specific roles
|
|
44
|
+
* - authenticated=false + roles → INVALID (caught at decorator time)
|
|
45
|
+
*/
|
|
46
|
+
class AuthMeta {
|
|
47
|
+
constructor(authenticated, roles) {
|
|
48
|
+
this.authenticated = authenticated;
|
|
49
|
+
this.roles = roles ?? [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.AuthMeta = AuthMeta;
|
|
53
|
+
/**
|
|
54
|
+
* @ApiPath(basePath) - Class decorator that marks a class as an API definition
|
|
55
|
+
* and sets the base path for all endpoints.
|
|
45
56
|
*
|
|
46
57
|
* Usage:
|
|
47
58
|
* ```typescript
|
|
48
|
-
* @
|
|
59
|
+
* @Authentication({authenticated: true})
|
|
60
|
+
* @ApiPath('/api/save')
|
|
49
61
|
* abstract class SaveApiPrototype {
|
|
50
|
-
* @
|
|
51
|
-
*
|
|
52
|
-
* save(request: SaveRequest): Promise<SaveResponse> {
|
|
53
|
-
* throw new Error('Must be implemented');
|
|
54
|
-
* }
|
|
62
|
+
* @Endpoint('/item')
|
|
63
|
+
* save(request: SaveRequest): Promise<SaveResponse> { ... }
|
|
55
64
|
* }
|
|
56
65
|
* ```
|
|
57
66
|
*/
|
|
58
|
-
function
|
|
67
|
+
function ApiPath(basePath) {
|
|
68
|
+
// webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
|
|
59
69
|
return (target) => {
|
|
60
|
-
Reflect.defineMetadata(exports.METADATA_KEYS.
|
|
61
|
-
// Initialize
|
|
62
|
-
if (!Reflect.hasMetadata(exports.METADATA_KEYS.
|
|
63
|
-
Reflect.defineMetadata(exports.METADATA_KEYS.
|
|
70
|
+
Reflect.defineMetadata(exports.METADATA_KEYS.API_PATH, basePath, target);
|
|
71
|
+
// Initialize endpoints map if not exists
|
|
72
|
+
if (!Reflect.hasMetadata(exports.METADATA_KEYS.ENDPOINTS, target)) {
|
|
73
|
+
Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINTS, {}, target);
|
|
64
74
|
}
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
78
|
+
* @Endpoint(path) - Method decorator that registers a POST endpoint at the given path.
|
|
79
|
+
*
|
|
80
|
+
* All endpoints are POST-only (matching gRPC/thrift style).
|
|
81
|
+
*
|
|
82
|
+
* Usage:
|
|
83
|
+
* ```typescript
|
|
84
|
+
* @Endpoint('/item')
|
|
85
|
+
* save(request: SaveRequest): Promise<SaveResponse> { ... }
|
|
86
|
+
* ```
|
|
70
87
|
*/
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// For instance methods, target is the prototype
|
|
88
|
+
function Endpoint(path) {
|
|
89
|
+
// webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
|
|
90
|
+
return (target, propertyKey, _descriptor) => {
|
|
75
91
|
const metadataTarget = typeof target === 'function' ? target : target.constructor;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!routeMetadata) {
|
|
80
|
-
routeMetadata = new RouteMetadata('', '', propertyKey);
|
|
81
|
-
existingMetadata.push(routeMetadata);
|
|
82
|
-
}
|
|
83
|
-
routeMetadata.httpMethod = method;
|
|
84
|
-
// Get parameter types
|
|
85
|
-
const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
|
|
86
|
-
if (paramTypes) {
|
|
87
|
-
routeMetadata.parameterTypes = paramTypes;
|
|
88
|
-
}
|
|
89
|
-
Reflect.defineMetadata(exports.METADATA_KEYS.ROUTES, existingMetadata, metadataTarget);
|
|
92
|
+
const endpoints = Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINTS, metadataTarget) || {};
|
|
93
|
+
endpoints[propertyKey] = path;
|
|
94
|
+
Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINTS, endpoints, metadataTarget);
|
|
90
95
|
};
|
|
91
96
|
}
|
|
92
97
|
/**
|
|
93
|
-
*
|
|
94
|
-
* Usage: @Get()
|
|
98
|
+
* Authentication config passed to @Authentication() decorator.
|
|
95
99
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
class AuthenticationConfig {
|
|
101
|
+
constructor(authenticated, roles) {
|
|
102
|
+
this.authenticated = authenticated;
|
|
103
|
+
this.roles = roles;
|
|
104
|
+
}
|
|
98
105
|
}
|
|
106
|
+
exports.AuthenticationConfig = AuthenticationConfig;
|
|
99
107
|
/**
|
|
100
|
-
* @
|
|
101
|
-
*
|
|
108
|
+
* @Authentication(config) - Class or method decorator for auth requirements.
|
|
109
|
+
*
|
|
110
|
+
* Single decorator replaces @Public/@Authenticated/@Roles:
|
|
111
|
+
* - @Authentication({authenticated: false}) → public, no auth check
|
|
112
|
+
* - @Authentication({authenticated: true}) → requires authentication
|
|
113
|
+
* - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles
|
|
114
|
+
*
|
|
115
|
+
* Class-level is required. Methods can override class-level.
|
|
116
|
+
* Throws if authenticated=false but roles are specified (contradictory).
|
|
102
117
|
*/
|
|
103
|
-
function
|
|
104
|
-
|
|
118
|
+
function Authentication(config) {
|
|
119
|
+
// Validate: can't be public with roles
|
|
120
|
+
if (!config.authenticated && config.roles && config.roles.length > 0) {
|
|
121
|
+
throw new Error(`Invalid @Authentication config: authenticated=false but roles=${JSON.stringify(config.roles)}. ` +
|
|
122
|
+
`Cannot require roles on a public endpoint. Set authenticated=true or remove roles.`);
|
|
123
|
+
}
|
|
124
|
+
const authMeta = new AuthMeta(config.authenticated, config.roles);
|
|
125
|
+
// webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any
|
|
126
|
+
return (target, propertyKey, _descriptor) => {
|
|
127
|
+
if (propertyKey !== undefined) {
|
|
128
|
+
// Method decorator
|
|
129
|
+
const metadataTarget = typeof target === 'function' ? target : target.constructor;
|
|
130
|
+
validateNoConflictingDecorators(metadataTarget, propertyKey);
|
|
131
|
+
Reflect.defineMetadata(exports.METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Class decorator
|
|
135
|
+
validateNoConflictingDecorators(target, undefined);
|
|
136
|
+
Reflect.defineMetadata(exports.METADATA_KEYS.AUTH_META, authMeta, target);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
105
139
|
}
|
|
140
|
+
// ============================================================
|
|
141
|
+
// Helper functions
|
|
142
|
+
// ============================================================
|
|
106
143
|
/**
|
|
107
|
-
*
|
|
108
|
-
* Usage: @Put()
|
|
144
|
+
* Get the base path from @ApiPath decorator.
|
|
109
145
|
*/
|
|
110
|
-
function
|
|
111
|
-
return
|
|
146
|
+
function getApiPath(apiClass) {
|
|
147
|
+
return Reflect.getMetadata(exports.METADATA_KEYS.API_PATH, apiClass);
|
|
112
148
|
}
|
|
113
149
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
150
|
+
* Get all endpoints from @Endpoint decorators.
|
|
151
|
+
* Returns a record of methodName -> endpoint path.
|
|
116
152
|
*/
|
|
117
|
-
function
|
|
118
|
-
return
|
|
153
|
+
function getEndpoints(apiClass) {
|
|
154
|
+
return Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINTS, apiClass);
|
|
119
155
|
}
|
|
120
156
|
/**
|
|
121
|
-
*
|
|
122
|
-
* Usage: @Patch()
|
|
157
|
+
* Check if a class has @ApiPath decorator.
|
|
123
158
|
*/
|
|
124
|
-
function
|
|
125
|
-
return
|
|
159
|
+
function isApiPath(apiClass) {
|
|
160
|
+
return Reflect.hasMetadata(exports.METADATA_KEYS.API_PATH, apiClass);
|
|
126
161
|
}
|
|
127
162
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* This decorator is used by:
|
|
132
|
-
* - Server: To register routes at the specified path
|
|
133
|
-
* - Client: To make HTTP requests to the specified path
|
|
134
|
-
*
|
|
135
|
-
* Usage:
|
|
136
|
-
* ```typescript
|
|
137
|
-
* @Post()
|
|
138
|
-
* @Path('/search/item')
|
|
139
|
-
* save(request: SaveRequest): Promise<SaveResponse> {
|
|
140
|
-
* throw new Error('Must be implemented');
|
|
141
|
-
* }
|
|
142
|
-
* ```
|
|
163
|
+
* Get auth metadata for a specific method, falling back to class-level auth.
|
|
164
|
+
* Method-level auth takes precedence over class-level auth.
|
|
143
165
|
*/
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Find or create route metadata for this method
|
|
151
|
-
let routeMetadata = existingMetadata.find((r) => r.methodName === propertyKey);
|
|
152
|
-
if (!routeMetadata) {
|
|
153
|
-
routeMetadata = new RouteMetadata('', '', propertyKey);
|
|
154
|
-
existingMetadata.push(routeMetadata);
|
|
166
|
+
function getAuthMeta(apiClass, methodName) {
|
|
167
|
+
// Check method-level first
|
|
168
|
+
if (methodName) {
|
|
169
|
+
const methodAuth = Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass, methodName);
|
|
170
|
+
if (methodAuth) {
|
|
171
|
+
return methodAuth;
|
|
155
172
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Helper function to get all routes from an API interface class.
|
|
162
|
-
* Used by both server-side routing and client-side client generation.
|
|
163
|
-
*/
|
|
164
|
-
function getRoutes(apiClass) {
|
|
165
|
-
const routes = Reflect.getMetadata(exports.METADATA_KEYS.ROUTES, apiClass);
|
|
166
|
-
return routes || [];
|
|
173
|
+
}
|
|
174
|
+
// Fall back to class-level
|
|
175
|
+
return Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass);
|
|
167
176
|
}
|
|
168
177
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
178
|
+
* Validate that a class/method doesn't have conflicting auth decorators.
|
|
179
|
+
* @throws Error if multiple @Authentication decorators are found on the same target.
|
|
171
180
|
*/
|
|
172
|
-
function
|
|
173
|
-
|
|
181
|
+
function validateNoConflictingDecorators(apiClass, methodName) {
|
|
182
|
+
const existing = methodName
|
|
183
|
+
? Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass, methodName)
|
|
184
|
+
: Reflect.getMetadata(exports.METADATA_KEYS.AUTH_META, apiClass);
|
|
185
|
+
if (existing) {
|
|
186
|
+
const targetName = apiClass.name || 'Unknown';
|
|
187
|
+
const location = methodName ? `method '${methodName}' of ${targetName}` : `class ${targetName}`;
|
|
188
|
+
throw new Error(`Conflicting @Authentication on ${location}. ` +
|
|
189
|
+
`Only one @Authentication() decorator allowed per target.`);
|
|
190
|
+
}
|
|
174
191
|
}
|
|
175
192
|
//# sourceMappingURL=decorators.js.map
|
package/src/decorators.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../../../packages/http/http-api/src/decorators.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\n\n/**\n * Metadata keys for storing API routing information.\n * These keys are used by both server-side (routing) and client-side (client generation).\n */\nexport const METADATA_KEYS = {\n API_INTERFACE: 'webpieces:api-interface',\n ROUTES: 'webpieces:routes',\n HTTP_METHOD: 'webpieces:http-method',\n PATH: 'webpieces:path',\n};\n\n/**\n * Route metadata stored on methods.\n * Used by both server-side routing and client-side HTTP client generation.\n */\nexport class RouteMetadata {\n httpMethod: string;\n path: string;\n methodName: string;\n parameterTypes?: any[];\n controllerClassName?: string;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n parameterTypes?: any[],\n controllerClassName?: string,\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.parameterTypes = parameterTypes;\n this.controllerClassName = controllerClassName;\n }\n}\n\n/**\n * Mark a class as an API interface.\n * Similar to Java's JAX-RS interface pattern.\n *\n * This decorator is used by:\n * - Server: RESTApiRoutes reads it to validate API interfaces\n * - Client: Client generator reads it to identify API interfaces\n *\n * Usage:\n * ```typescript\n * @ApiInterface()\n * abstract class SaveApiPrototype {\n * @Post()\n * @Path('/search/item')\n * save(request: SaveRequest): Promise<SaveResponse> {\n * throw new Error('Must be implemented');\n * }\n * }\n * ```\n */\nexport function ApiInterface(): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_INTERFACE, true, target);\n\n // Initialize routes array if not exists\n if (!Reflect.hasMetadata(METADATA_KEYS.ROUTES, target)) {\n Reflect.defineMetadata(METADATA_KEYS.ROUTES, [], target);\n }\n };\n}\n\n/**\n * Internal helper to mark a method with an HTTP method.\n * Used by @Get, @Post, @Put, @Delete, @Patch decorators.\n */\nfunction httpMethod(method: string): MethodDecorator {\n return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n // For static methods, target is the constructor itself\n // For instance methods, target is the prototype\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n\n const existingMetadata = Reflect.getMetadata(METADATA_KEYS.ROUTES, metadataTarget) || [];\n\n // Find or create route metadata for this method\n let routeMetadata = existingMetadata.find(\n (r: RouteMetadata) => r.methodName === propertyKey,\n );\n\n if (!routeMetadata) {\n routeMetadata = new RouteMetadata('', '', propertyKey as string);\n existingMetadata.push(routeMetadata);\n }\n\n routeMetadata.httpMethod = method;\n\n // Get parameter types\n const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);\n if (paramTypes) {\n routeMetadata.parameterTypes = paramTypes;\n }\n\n Reflect.defineMetadata(METADATA_KEYS.ROUTES, existingMetadata, metadataTarget);\n };\n}\n\n/**\n * @Get decorator for GET requests.\n * Usage: @Get()\n */\nexport function Get(): MethodDecorator {\n return httpMethod('GET');\n}\n\n/**\n * @Post decorator for POST requests.\n * Usage: @Post()\n */\nexport function Post(): MethodDecorator {\n return httpMethod('POST');\n}\n\n/**\n * @Put decorator for PUT requests.\n * Usage: @Put()\n */\nexport function Put(): MethodDecorator {\n return httpMethod('PUT');\n}\n\n/**\n * @Delete decorator for DELETE requests.\n * Usage: @Delete()\n */\nexport function Delete(): MethodDecorator {\n return httpMethod('DELETE');\n}\n\n/**\n * @Patch decorator for PATCH requests.\n * Usage: @Patch()\n */\nexport function Patch(): MethodDecorator {\n return httpMethod('PATCH');\n}\n\n/**\n * @Path decorator to specify the route path.\n * Similar to JAX-RS @Path annotation.\n *\n * This decorator is used by:\n * - Server: To register routes at the specified path\n * - Client: To make HTTP requests to the specified path\n *\n * Usage:\n * ```typescript\n * @Post()\n * @Path('/search/item')\n * save(request: SaveRequest): Promise<SaveResponse> {\n * throw new Error('Must be implemented');\n * }\n * ```\n */\nexport function Path(path: string): MethodDecorator {\n return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n // For static methods, target is the constructor itself\n // For instance methods, target is the prototype\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n\n const existingMetadata = Reflect.getMetadata(METADATA_KEYS.ROUTES, metadataTarget) || [];\n\n // Find or create route metadata for this method\n let routeMetadata = existingMetadata.find(\n (r: RouteMetadata) => r.methodName === propertyKey,\n );\n\n if (!routeMetadata) {\n routeMetadata = new RouteMetadata('', '', propertyKey as string);\n existingMetadata.push(routeMetadata);\n }\n\n routeMetadata.path = path;\n\n Reflect.defineMetadata(METADATA_KEYS.ROUTES, existingMetadata, metadataTarget);\n };\n}\n\n/**\n * Helper function to get all routes from an API interface class.\n * Used by both server-side routing and client-side client generation.\n */\nexport function getRoutes(apiClass: any): RouteMetadata[] {\n const routes = Reflect.getMetadata(METADATA_KEYS.ROUTES, apiClass);\n return routes || [];\n}\n\n/**\n * Helper function to check if a class is an API interface.\n * Used by both server-side routing and client-side client generation.\n */\nexport function isApiInterface(apiClass: any): boolean {\n return Reflect.getMetadata(METADATA_KEYS.API_INTERFACE, apiClass) === true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../../../packages/http/http-api/src/decorators.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\n\n/**\n * Metadata keys for storing API routing information.\n * These keys are used by both server-side (routing) and client-side (client generation).\n */\nexport const METADATA_KEYS = {\n API_PATH: 'webpieces:api-path',\n ENDPOINTS: 'webpieces:endpoints',\n AUTH_META: 'webpieces:auth-meta',\n};\n\n/**\n * Route metadata stored per-method at runtime.\n * Used internally by http-routing and http-client as the runtime representation\n * of a route. Constructed from @ApiPath + @Endpoint metadata by createApiClient\n * and ApiRoutingFactory.\n */\nexport class RouteMetadata {\n httpMethod: string;\n path: string;\n methodName: string;\n controllerClassName?: string;\n authMeta?: AuthMeta;\n\n constructor(\n httpMethod: string,\n path: string,\n methodName: string,\n controllerClassName?: string,\n authMeta?: AuthMeta,\n ) {\n this.httpMethod = httpMethod;\n this.path = path;\n this.methodName = methodName;\n this.controllerClassName = controllerClassName;\n this.authMeta = authMeta;\n }\n}\n\n/**\n * Auth metadata attached to a class or method via @Authentication().\n *\n * - authenticated=false → public endpoint (no auth check)\n * - authenticated=true, no roles → requires authentication\n * - authenticated=true, roles=['admin'] → requires authentication + specific roles\n * - authenticated=false + roles → INVALID (caught at decorator time)\n */\nexport class AuthMeta {\n authenticated: boolean;\n roles: string[];\n\n constructor(authenticated: boolean, roles?: string[]) {\n this.authenticated = authenticated;\n this.roles = roles ?? [];\n }\n}\n\n/**\n * @ApiPath(basePath) - Class decorator that marks a class as an API definition\n * and sets the base path for all endpoints.\n *\n * Usage:\n * ```typescript\n * @Authentication({authenticated: true})\n * @ApiPath('/api/save')\n * abstract class SaveApiPrototype {\n * @Endpoint('/item')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n * }\n * ```\n */\nexport function ApiPath(basePath: string): ClassDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any) => {\n Reflect.defineMetadata(METADATA_KEYS.API_PATH, basePath, target);\n\n // Initialize endpoints map if not exists\n if (!Reflect.hasMetadata(METADATA_KEYS.ENDPOINTS, target)) {\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINTS, {}, target);\n }\n };\n}\n\n/**\n * @Endpoint(path) - Method decorator that registers a POST endpoint at the given path.\n *\n * All endpoints are POST-only (matching gRPC/thrift style).\n *\n * Usage:\n * ```typescript\n * @Endpoint('/item')\n * save(request: SaveRequest): Promise<SaveResponse> { ... }\n * ```\n */\nexport function Endpoint(path: string): MethodDecorator {\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey: string | symbol, _descriptor: PropertyDescriptor) => {\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n\n const endpoints: Record<string, string> =\n Reflect.getMetadata(METADATA_KEYS.ENDPOINTS, metadataTarget) || {};\n\n endpoints[propertyKey as string] = path;\n\n Reflect.defineMetadata(METADATA_KEYS.ENDPOINTS, endpoints, metadataTarget);\n };\n}\n\n/**\n * Authentication config passed to @Authentication() decorator.\n */\nexport class AuthenticationConfig {\n authenticated: boolean;\n roles?: string[];\n\n constructor(authenticated: boolean, roles?: string[]) {\n this.authenticated = authenticated;\n this.roles = roles;\n }\n}\n\n/**\n * @Authentication(config) - Class or method decorator for auth requirements.\n *\n * Single decorator replaces @Public/@Authenticated/@Roles:\n * - @Authentication({authenticated: false}) → public, no auth check\n * - @Authentication({authenticated: true}) → requires authentication\n * - @Authentication({authenticated: true, roles: ['admin']}) → requires auth + roles\n *\n * Class-level is required. Methods can override class-level.\n * Throws if authenticated=false but roles are specified (contradictory).\n */\nexport function Authentication(config: AuthenticationConfig): ClassDecorator & MethodDecorator {\n // Validate: can't be public with roles\n if (!config.authenticated && config.roles && config.roles.length > 0) {\n throw new Error(\n `Invalid @Authentication config: authenticated=false but roles=${JSON.stringify(config.roles)}. ` +\n `Cannot require roles on a public endpoint. Set authenticated=true or remove roles.`\n );\n }\n\n const authMeta = new AuthMeta(config.authenticated, config.roles);\n\n // webpieces-disable no-any-unknown -- reflect-metadata decorator API requires any\n return (target: any, propertyKey?: string | symbol, _descriptor?: PropertyDescriptor) => {\n if (propertyKey !== undefined) {\n // Method decorator\n const metadataTarget = typeof target === 'function' ? target : target.constructor;\n validateNoConflictingDecorators(metadataTarget, propertyKey as string);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, metadataTarget, propertyKey);\n } else {\n // Class decorator\n validateNoConflictingDecorators(target, undefined);\n Reflect.defineMetadata(METADATA_KEYS.AUTH_META, authMeta, target);\n }\n };\n}\n\n// ============================================================\n// Helper functions\n// ============================================================\n\n/**\n * Get the base path from @ApiPath decorator.\n */\nexport function getApiPath(apiClass: Function): string | undefined {\n return Reflect.getMetadata(METADATA_KEYS.API_PATH, apiClass);\n}\n\n/**\n * Get all endpoints from @Endpoint decorators.\n * Returns a record of methodName -> endpoint path.\n */\nexport function getEndpoints(apiClass: Function): Record<string, string> | undefined {\n return Reflect.getMetadata(METADATA_KEYS.ENDPOINTS, apiClass);\n}\n\n/**\n * Check if a class has @ApiPath decorator.\n */\nexport function isApiPath(apiClass: Function): boolean {\n return Reflect.hasMetadata(METADATA_KEYS.API_PATH, apiClass);\n}\n\n/**\n * Get auth metadata for a specific method, falling back to class-level auth.\n * Method-level auth takes precedence over class-level auth.\n */\nexport function getAuthMeta(apiClass: Function, methodName?: string): AuthMeta | undefined {\n // Check method-level first\n if (methodName) {\n const methodAuth = Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName);\n if (methodAuth) {\n return methodAuth;\n }\n }\n\n // Fall back to class-level\n return Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass);\n}\n\n/**\n * Validate that a class/method doesn't have conflicting auth decorators.\n * @throws Error if multiple @Authentication decorators are found on the same target.\n */\nexport function validateNoConflictingDecorators(apiClass: Function, methodName: string | undefined): void {\n const existing = methodName\n ? Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass, methodName)\n : Reflect.getMetadata(METADATA_KEYS.AUTH_META, apiClass);\n\n if (existing) {\n const targetName = apiClass.name || 'Unknown';\n const location = methodName ? `method '${methodName}' of ${targetName}` : `class ${targetName}`;\n throw new Error(\n `Conflicting @Authentication on ${location}. ` +\n `Only one @Authentication() decorator allowed per target.`\n );\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* └── http-client (client: contract → HTTP requests)
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export {
|
|
19
|
+
export { ApiPath, Endpoint, Authentication, AuthenticationConfig, getApiPath, getEndpoints, isApiPath, getAuthMeta, validateNoConflictingDecorators, AuthMeta, RouteMetadata, METADATA_KEYS, } from './decorators';
|
|
20
20
|
export { ValidateImplementation } from './validators';
|
|
21
21
|
export { ProtocolError, HttpError, HttpNotFoundError, EndpointNotFoundError, HttpBadRequestError, HttpUnauthorizedError, HttpForbiddenError, HttpTimeoutError, HttpBadGatewayError, HttpGatewayTimeoutError, HttpInternalServerError, HttpVendorError, HttpUserError, ENTITY_NOT_FOUND, WRONG_LOGIN_TYPE, WRONG_LOGIN, NOT_APPROVED, EMAIL_NOT_CONFIRMED, WRONG_DOMAIN, WRONG_COMPANY, NO_REG_CODE, } from './errors';
|
|
22
22
|
export { InstantDto, DateDto, TimeDto, DateTimeDto, InstantUtil, DateUtil, TimeUtil, DateTimeUtil, } from './datetime';
|
package/src/index.js
CHANGED
|
@@ -18,18 +18,19 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.LogApiCall = exports.HEADER_TYPES = exports.HeaderMethods = exports.PlatformHeadersExtension = exports.PlatformHeader = exports.DateTimeUtil = exports.TimeUtil = exports.DateUtil = exports.InstantUtil = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = exports.ENTITY_NOT_FOUND = exports.HttpUserError = exports.HttpVendorError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = exports.ProtocolError = exports.METADATA_KEYS = exports.RouteMetadata = exports.
|
|
21
|
+
exports.LogApiCall = exports.HEADER_TYPES = exports.HeaderMethods = exports.PlatformHeadersExtension = exports.PlatformHeader = exports.DateTimeUtil = exports.TimeUtil = exports.DateUtil = exports.InstantUtil = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = exports.ENTITY_NOT_FOUND = exports.HttpUserError = exports.HttpVendorError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = exports.ProtocolError = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.validateNoConflictingDecorators = exports.getAuthMeta = exports.isApiPath = exports.getEndpoints = exports.getApiPath = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = void 0;
|
|
22
22
|
// API definition decorators
|
|
23
23
|
var decorators_1 = require("./decorators");
|
|
24
|
-
Object.defineProperty(exports, "
|
|
25
|
-
Object.defineProperty(exports, "
|
|
26
|
-
Object.defineProperty(exports, "
|
|
27
|
-
Object.defineProperty(exports, "
|
|
28
|
-
Object.defineProperty(exports, "
|
|
29
|
-
Object.defineProperty(exports, "
|
|
30
|
-
Object.defineProperty(exports, "
|
|
31
|
-
Object.defineProperty(exports, "
|
|
32
|
-
Object.defineProperty(exports, "
|
|
24
|
+
Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return decorators_1.ApiPath; } });
|
|
25
|
+
Object.defineProperty(exports, "Endpoint", { enumerable: true, get: function () { return decorators_1.Endpoint; } });
|
|
26
|
+
Object.defineProperty(exports, "Authentication", { enumerable: true, get: function () { return decorators_1.Authentication; } });
|
|
27
|
+
Object.defineProperty(exports, "AuthenticationConfig", { enumerable: true, get: function () { return decorators_1.AuthenticationConfig; } });
|
|
28
|
+
Object.defineProperty(exports, "getApiPath", { enumerable: true, get: function () { return decorators_1.getApiPath; } });
|
|
29
|
+
Object.defineProperty(exports, "getEndpoints", { enumerable: true, get: function () { return decorators_1.getEndpoints; } });
|
|
30
|
+
Object.defineProperty(exports, "isApiPath", { enumerable: true, get: function () { return decorators_1.isApiPath; } });
|
|
31
|
+
Object.defineProperty(exports, "getAuthMeta", { enumerable: true, get: function () { return decorators_1.getAuthMeta; } });
|
|
32
|
+
Object.defineProperty(exports, "validateNoConflictingDecorators", { enumerable: true, get: function () { return decorators_1.validateNoConflictingDecorators; } });
|
|
33
|
+
Object.defineProperty(exports, "AuthMeta", { enumerable: true, get: function () { return decorators_1.AuthMeta; } });
|
|
33
34
|
Object.defineProperty(exports, "RouteMetadata", { enumerable: true, get: function () { return decorators_1.RouteMetadata; } });
|
|
34
35
|
Object.defineProperty(exports, "METADATA_KEYS", { enumerable: true, get: function () { return decorators_1.METADATA_KEYS; } });
|
|
35
36
|
// HTTP errors
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-api/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAEH,4BAA4B;AAC5B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-api/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAEH,4BAA4B;AAC5B,2CAasB;AAZlB,qGAAA,OAAO,OAAA;AACP,sGAAA,QAAQ,OAAA;AACR,4GAAA,cAAc,OAAA;AACd,kHAAA,oBAAoB,OAAA;AACpB,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,uGAAA,SAAS,OAAA;AACT,yGAAA,WAAW,OAAA;AACX,6HAAA,+BAA+B,OAAA;AAC/B,sGAAA,QAAQ,OAAA;AACR,2GAAA,aAAa,OAAA;AACb,2GAAA,aAAa,OAAA;AAMjB,cAAc;AACd,mCAuBkB;AAtBd,uGAAA,aAAa,OAAA;AACb,mGAAA,SAAS,OAAA;AACT,2GAAA,iBAAiB,OAAA;AACjB,+GAAA,qBAAqB,OAAA;AACrB,6GAAA,mBAAmB,OAAA;AACnB,+GAAA,qBAAqB,OAAA;AACrB,4GAAA,kBAAkB,OAAA;AAClB,0GAAA,gBAAgB,OAAA;AAChB,6GAAA,mBAAmB,OAAA;AACnB,iHAAA,uBAAuB,OAAA;AACvB,iHAAA,uBAAuB,OAAA;AACvB,yGAAA,eAAe,OAAA;AACf,uGAAA,aAAa,OAAA;AACb,0BAA0B;AAC1B,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA;AAChB,qGAAA,WAAW,OAAA;AACX,sGAAA,YAAY,OAAA;AACZ,6GAAA,mBAAmB,OAAA;AACnB,sGAAA,YAAY,OAAA;AACZ,uGAAA,aAAa,OAAA;AACb,qGAAA,WAAW,OAAA;AAGf,iEAAiE;AACjE,uCASoB;AAJhB,uGAAA,WAAW,OAAA;AACX,oGAAA,QAAQ,OAAA;AACR,oGAAA,QAAQ,OAAA;AACR,wGAAA,YAAY,OAAA;AAGhB,mBAAmB;AACnB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,iDAA+D;AAAtD,8GAAA,aAAa,OAAA;AACtB,6CAA6C;AAApC,2GAAA,YAAY,OAAA;AAErB,UAAU;AACV,2CAA0C;AAAjC,wGAAA,UAAU,OAAA","sourcesContent":["/**\n * @webpieces/http-api\n *\n * Core HTTP API definition package.\n * Contains decorators and utilities for defining HTTP APIs.\n *\n * This package is used by:\n * - @webpieces/http-routing (server-side): Routes HTTP requests to controllers\n * - @webpieces/http-client (client-side): Generates HTTP clients from API definitions\n *\n * Architecture:\n * ```\n * http-api (defines the contract)\n * ↑\n * ├── http-routing (server: contract → handlers)\n * └── http-client (client: contract → HTTP requests)\n * ```\n */\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './decorators';\n\n// Type validators\nexport { ValidateImplementation } from './validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpVendorError,\n HttpUserError,\n // Error subtype constants\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './errors';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './datetime';\n\n// Platform Headers\nexport { PlatformHeader } from './PlatformHeader';\nexport { PlatformHeadersExtension } from './PlatformHeadersExtension';\nexport { HeaderMethods, ContextReader } from './HeaderMethods';\nexport { HEADER_TYPES } from './HeaderTypes';\n\n// Logging\nexport { LogApiCall } from './LogApiCall';\n"]}
|