glasswork 0.1.0
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/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/bootstrap.d.ts +55 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +359 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/core/bootstrap.d.ts +55 -0
- package/dist/core/bootstrap.d.ts.map +1 -0
- package/dist/core/bootstrap.js +329 -0
- package/dist/core/bootstrap.js.map +1 -0
- package/dist/core/module.d.ts +21 -0
- package/dist/core/module.d.ts.map +1 -0
- package/dist/core/module.js +85 -0
- package/dist/core/module.js.map +1 -0
- package/dist/core/types.d.ts +273 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/environment.d.ts +21 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +29 -0
- package/dist/environment.js.map +1 -0
- package/dist/error-handler.d.ts +33 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +53 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +65 -0
- package/dist/errors.js.map +1 -0
- package/dist/get-client-ip.d.ts +24 -0
- package/dist/get-client-ip.d.ts.map +1 -0
- package/dist/get-client-ip.js +49 -0
- package/dist/get-client-ip.js.map +1 -0
- package/dist/http/error-handler.d.ts +33 -0
- package/dist/http/error-handler.d.ts.map +1 -0
- package/dist/http/error-handler.js +55 -0
- package/dist/http/error-handler.js.map +1 -0
- package/dist/http/errors.d.ts +36 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +65 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/route-helpers.d.ts +172 -0
- package/dist/http/route-helpers.d.ts.map +1 -0
- package/dist/http/route-helpers.js +249 -0
- package/dist/http/route-helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +37 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +52 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +7 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +182 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/module.d.ts +21 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +85 -0
- package/dist/module.js.map +1 -0
- package/dist/omit.d.ts +20 -0
- package/dist/omit.d.ts.map +1 -0
- package/dist/omit.js +28 -0
- package/dist/omit.js.map +1 -0
- package/dist/openapi/defaults.d.ts +11 -0
- package/dist/openapi/defaults.d.ts.map +1 -0
- package/dist/openapi/defaults.js +127 -0
- package/dist/openapi/defaults.js.map +1 -0
- package/dist/openapi/hooks.d.ts +14 -0
- package/dist/openapi/hooks.d.ts.map +1 -0
- package/dist/openapi/hooks.js +80 -0
- package/dist/openapi/hooks.js.map +1 -0
- package/dist/openapi/openapi.d.ts +24 -0
- package/dist/openapi/openapi.d.ts.map +1 -0
- package/dist/openapi/openapi.js +108 -0
- package/dist/openapi/openapi.js.map +1 -0
- package/dist/openapi-defaults.d.ts +11 -0
- package/dist/openapi-defaults.d.ts.map +1 -0
- package/dist/openapi-defaults.js +127 -0
- package/dist/openapi-defaults.js.map +1 -0
- package/dist/openapi-hooks.d.ts +14 -0
- package/dist/openapi-hooks.d.ts.map +1 -0
- package/dist/openapi-hooks.js +80 -0
- package/dist/openapi-hooks.js.map +1 -0
- package/dist/openapi.d.ts +24 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +106 -0
- package/dist/openapi.js.map +1 -0
- package/dist/pagination.d.ts +48 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +43 -0
- package/dist/pagination.js.map +1 -0
- package/dist/pick.d.ts +20 -0
- package/dist/pick.d.ts.map +1 -0
- package/dist/pick.js +28 -0
- package/dist/pick.js.map +1 -0
- package/dist/rate-limit.d.ts +7 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +180 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/route-helpers.d.ts +172 -0
- package/dist/route-helpers.d.ts.map +1 -0
- package/dist/route-helpers.js +249 -0
- package/dist/route-helpers.js.map +1 -0
- package/dist/serialize-dates.d.ts +27 -0
- package/dist/serialize-dates.d.ts.map +1 -0
- package/dist/serialize-dates.js +39 -0
- package/dist/serialize-dates.js.map +1 -0
- package/dist/testing.d.ts +87 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +94 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +273 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/deep-merge.d.ts +5 -0
- package/dist/utils/deep-merge.d.ts.map +1 -0
- package/dist/utils/deep-merge.js +21 -0
- package/dist/utils/deep-merge.js.map +1 -0
- package/dist/utils/environment.d.ts +21 -0
- package/dist/utils/environment.d.ts.map +1 -0
- package/dist/utils/environment.js +29 -0
- package/dist/utils/environment.js.map +1 -0
- package/dist/utils/get-client-ip.d.ts +24 -0
- package/dist/utils/get-client-ip.d.ts.map +1 -0
- package/dist/utils/get-client-ip.js +49 -0
- package/dist/utils/get-client-ip.js.map +1 -0
- package/dist/utils/logger.d.ts +37 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +52 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/omit.d.ts +20 -0
- package/dist/utils/omit.d.ts.map +1 -0
- package/dist/utils/omit.js +28 -0
- package/dist/utils/omit.js.map +1 -0
- package/dist/utils/pagination.d.ts +48 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +42 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/pick.d.ts +20 -0
- package/dist/utils/pick.d.ts.map +1 -0
- package/dist/utils/pick.js +28 -0
- package/dist/utils/pick.js.map +1 -0
- package/dist/utils/serialize-dates.d.ts +27 -0
- package/dist/utils/serialize-dates.d.ts.map +1 -0
- package/dist/utils/serialize-dates.js +39 -0
- package/dist/utils/serialize-dates.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +31 -0
- package/dist/utils.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/openapi/defaults.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OpenAPIResponseHook } from '../core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a hook that adds CORS headers to responses
|
|
4
|
+
*/
|
|
5
|
+
export declare function createCorsHeadersHook(enabled: boolean): OpenAPIResponseHook;
|
|
6
|
+
/**
|
|
7
|
+
* Create a hook that adds rate limiting headers to responses
|
|
8
|
+
*/
|
|
9
|
+
export declare function createRateLimitHeadersHook(enabled: boolean): OpenAPIResponseHook;
|
|
10
|
+
/**
|
|
11
|
+
* Create a hook that adds pagination headers to 200 responses
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPaginationHeadersHook(): OpenAPIResponseHook;
|
|
14
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/openapi/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAc3E;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAgChF;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,mBAAmB,CAyBjE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a hook that adds CORS headers to responses
|
|
3
|
+
*/
|
|
4
|
+
export function createCorsHeadersHook(enabled) {
|
|
5
|
+
return (response, _context) => {
|
|
6
|
+
if (!enabled)
|
|
7
|
+
return response;
|
|
8
|
+
return {
|
|
9
|
+
...response,
|
|
10
|
+
headers: {
|
|
11
|
+
...response.headers,
|
|
12
|
+
'Access-Control-Allow-Origin': {
|
|
13
|
+
$ref: '#/components/headers/Access-Control-Allow-Origin',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a hook that adds rate limiting headers to responses
|
|
21
|
+
*/
|
|
22
|
+
export function createRateLimitHeadersHook(enabled) {
|
|
23
|
+
return (response, context) => {
|
|
24
|
+
if (!enabled)
|
|
25
|
+
return response;
|
|
26
|
+
const { statusCode } = context;
|
|
27
|
+
const headers = {
|
|
28
|
+
'RateLimit-Limit': {
|
|
29
|
+
$ref: '#/components/headers/RateLimit-Limit',
|
|
30
|
+
},
|
|
31
|
+
'RateLimit-Remaining': {
|
|
32
|
+
$ref: '#/components/headers/RateLimit-Remaining',
|
|
33
|
+
},
|
|
34
|
+
'RateLimit-Reset': {
|
|
35
|
+
$ref: '#/components/headers/RateLimit-Reset',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
// Add Retry-After header for 429 responses
|
|
39
|
+
if (statusCode === '429') {
|
|
40
|
+
headers['Retry-After'] = {
|
|
41
|
+
$ref: '#/components/headers/Retry-After',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...response,
|
|
46
|
+
headers: {
|
|
47
|
+
...response.headers,
|
|
48
|
+
...headers,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a hook that adds pagination headers to 200 responses
|
|
55
|
+
*/
|
|
56
|
+
export function createPaginationHeadersHook() {
|
|
57
|
+
return (response, context) => {
|
|
58
|
+
const { statusCode, routeConfig } = context;
|
|
59
|
+
// Only add pagination headers to 200 responses with paginate flag
|
|
60
|
+
if (statusCode !== '200' || !routeConfig.paginate) {
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...response,
|
|
65
|
+
headers: {
|
|
66
|
+
...response.headers,
|
|
67
|
+
'X-Total-Count': {
|
|
68
|
+
$ref: '#/components/headers/X-Total-Count',
|
|
69
|
+
},
|
|
70
|
+
'X-Page': {
|
|
71
|
+
$ref: '#/components/headers/X-Page',
|
|
72
|
+
},
|
|
73
|
+
'X-Limit': {
|
|
74
|
+
$ref: '#/components/headers/X-Limit',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/openapi/hooks.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAE9B,OAAO;YACL,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,6BAA6B,EAAE;oBAC7B,IAAI,EAAE,kDAAkD;iBACzD;aACF;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAgB;IACzD,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC3B,IAAI,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;QAC/B,MAAM,OAAO,GAAqC;YAChD,iBAAiB,EAAE;gBACjB,IAAI,EAAE,sCAAsC;aAC7C;YACD,qBAAqB,EAAE;gBACrB,IAAI,EAAE,0CAA0C;aACjD;YACD,iBAAiB,EAAE;gBACjB,IAAI,EAAE,sCAAsC;aAC7C;SACF,CAAC;QAEF,2CAA2C;QAC3C,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;YACzB,OAAO,CAAC,aAAa,CAAC,GAAG;gBACvB,IAAI,EAAE,kCAAkC;aACzC,CAAC;QACJ,CAAC;QAED,OAAO;YACL,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,GAAG,OAAO;aACX;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC3B,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAE5C,kEAAkE;QAClE,IAAI,UAAU,KAAK,KAAK,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;YAClD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,OAAO;YACL,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,eAAe,EAAE;oBACf,IAAI,EAAE,oCAAoC;iBAC3C;gBACD,QAAQ,EAAE;oBACR,IAAI,EAAE,6BAA6B;iBACpC;gBACD,SAAS,EAAE;oBACT,IAAI,EAAE,8BAA8B;iBACrC;aACF;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { Environment, MiddlewareOptions, OpenAPIOptions, RateLimitOptions } from '../core/types.js';
|
|
3
|
+
export interface ConfigureOpenAPIOptions {
|
|
4
|
+
app: Hono;
|
|
5
|
+
environment: Environment;
|
|
6
|
+
openapi: OpenAPIOptions;
|
|
7
|
+
rateLimit?: RateLimitOptions;
|
|
8
|
+
middleware?: MiddlewareOptions;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Configure OpenAPI documentation for the application.
|
|
12
|
+
*
|
|
13
|
+
* Behavior by environment:
|
|
14
|
+
* - development: Serve specs and UI at /api and /api/openapi.json
|
|
15
|
+
* - production: Write spec to disk (openapi.json), no serving
|
|
16
|
+
* - test: Disabled by default
|
|
17
|
+
*
|
|
18
|
+
* This function automatically sets up response hooks based on enabled features
|
|
19
|
+
* and merges them with any application-provided hooks.
|
|
20
|
+
*
|
|
21
|
+
* @param options - Configuration options
|
|
22
|
+
*/
|
|
23
|
+
export declare function configureOpenAPI(options: ConfigureOpenAPIOptions): void;
|
|
24
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAW1B,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,IAAI,CAAC;IACV,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAkEvE"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { swaggerUI } from '@hono/swagger-ui';
|
|
3
|
+
import { openAPISpecs } from 'hono-openapi';
|
|
4
|
+
import { setGlobalResponseHooks } from '../http/route-helpers.js';
|
|
5
|
+
import { deepMerge } from '../utils/deep-merge.js';
|
|
6
|
+
import { createLogger } from '../utils/logger.js';
|
|
7
|
+
import { defaultOpenAPIComponents } from './defaults.js';
|
|
8
|
+
import { createCorsHeadersHook, createPaginationHeadersHook, createRateLimitHeadersHook, } from './hooks.js';
|
|
9
|
+
/**
|
|
10
|
+
* Configure OpenAPI documentation for the application.
|
|
11
|
+
*
|
|
12
|
+
* Behavior by environment:
|
|
13
|
+
* - development: Serve specs and UI at /api and /api/openapi.json
|
|
14
|
+
* - production: Write spec to disk (openapi.json), no serving
|
|
15
|
+
* - test: Disabled by default
|
|
16
|
+
*
|
|
17
|
+
* This function automatically sets up response hooks based on enabled features
|
|
18
|
+
* and merges them with any application-provided hooks.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Configuration options
|
|
21
|
+
*/
|
|
22
|
+
export function configureOpenAPI(options) {
|
|
23
|
+
const { app, environment, openapi, rateLimit, middleware } = options;
|
|
24
|
+
if (!openapi.enabled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const logger = createLogger('Glasswork:OpenAPI', true);
|
|
28
|
+
const shouldServeSpecs = openapi.serveSpecs ?? environment === 'development';
|
|
29
|
+
const shouldServeUI = openapi.serveUI ?? environment === 'development';
|
|
30
|
+
// Build response hooks based on enabled features
|
|
31
|
+
const frameworkHooks = [
|
|
32
|
+
createCorsHeadersHook(!!middleware?.cors),
|
|
33
|
+
createRateLimitHeadersHook(rateLimit?.enabled ?? false),
|
|
34
|
+
createPaginationHeadersHook(),
|
|
35
|
+
];
|
|
36
|
+
// Merge with application-provided hooks
|
|
37
|
+
const allHooks = [...frameworkHooks, ...(openapi.responseHooks || [])];
|
|
38
|
+
// Make hooks available to route definitions globally
|
|
39
|
+
// This must be done synchronously before routes are mounted
|
|
40
|
+
setGlobalResponseHooks(allHooks);
|
|
41
|
+
// Filter components based on enabled features (only include headers that are actually used)
|
|
42
|
+
const filteredComponents = filterComponentsByFeatures(defaultOpenAPIComponents, {
|
|
43
|
+
rateLimitEnabled: rateLimit?.enabled ?? false,
|
|
44
|
+
corsEnabled: !!middleware?.cors,
|
|
45
|
+
});
|
|
46
|
+
// Merge application documentation with filtered Glasswork defaults
|
|
47
|
+
const mergedDocumentation = deepMerge({ components: filteredComponents }, openapi.documentation || {});
|
|
48
|
+
// Serve OpenAPI spec endpoint (development only by default)
|
|
49
|
+
if (shouldServeSpecs) {
|
|
50
|
+
app.get('/api/openapi.json', openAPISpecs(app, { documentation: mergedDocumentation }));
|
|
51
|
+
}
|
|
52
|
+
// Serve Swagger UI (development only by default)
|
|
53
|
+
if (shouldServeUI) {
|
|
54
|
+
app.get('/api', swaggerUI({ url: '/api/openapi.json' }));
|
|
55
|
+
}
|
|
56
|
+
// Write OpenAPI spec to file (production/Lambda)
|
|
57
|
+
if (openapi.writeToFile) {
|
|
58
|
+
const filePath = openapi.writeToFile;
|
|
59
|
+
// Generate spec and write to file
|
|
60
|
+
// This happens after routes are registered
|
|
61
|
+
setTimeout(async () => {
|
|
62
|
+
try {
|
|
63
|
+
// Use Hono's built-in request method to simulate the request
|
|
64
|
+
const response = await app.request('/api/openapi.json');
|
|
65
|
+
const specContent = await response.text();
|
|
66
|
+
await writeFile(filePath, specContent, 'utf-8');
|
|
67
|
+
logger.info(`OpenAPI spec written to ${filePath}`);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.error('Failed to write OpenAPI spec:', error);
|
|
71
|
+
}
|
|
72
|
+
}, 1000); // Wait for routes to be registered
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Filter component headers based on enabled features.
|
|
77
|
+
* This removes header definitions from the components schema that won't be used.
|
|
78
|
+
*/
|
|
79
|
+
function filterComponentsByFeatures(components, features) {
|
|
80
|
+
if (!components.headers) {
|
|
81
|
+
return components;
|
|
82
|
+
}
|
|
83
|
+
const filteredHeaders = Object.fromEntries(Object.entries(components.headers).filter(([name]) => shouldIncludeHeader(name, features)));
|
|
84
|
+
return {
|
|
85
|
+
...components,
|
|
86
|
+
headers: filteredHeaders,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Determine if a header should be included based on feature configuration
|
|
91
|
+
*/
|
|
92
|
+
function shouldIncludeHeader(headerName, { rateLimitEnabled, corsEnabled }) {
|
|
93
|
+
// Always include pagination headers (they're route-specific, not feature-gated)
|
|
94
|
+
if (headerName.startsWith('X-')) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// Include CORS headers only if CORS is enabled
|
|
98
|
+
if (headerName === 'Access-Control-Allow-Origin') {
|
|
99
|
+
return corsEnabled;
|
|
100
|
+
}
|
|
101
|
+
// Include rate limit headers only if rate limiting is enabled
|
|
102
|
+
if (headerName.startsWith('RateLimit-') || headerName === 'Retry-After') {
|
|
103
|
+
return rateLimitEnabled;
|
|
104
|
+
}
|
|
105
|
+
// Include other headers by default
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/openapi/openapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
/**
|
|
3
|
+
* Default OpenAPI components provided by Glasswork.
|
|
4
|
+
* These include headers for features implemented by the framework:
|
|
5
|
+
* - Rate limiting headers
|
|
6
|
+
* - Pagination headers
|
|
7
|
+
* - CORS headers
|
|
8
|
+
* - Error response schemas
|
|
9
|
+
*/
|
|
10
|
+
export declare const defaultOpenAPIComponents: OpenAPIV3.ComponentsObject;
|
|
11
|
+
//# sourceMappingURL=openapi-defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-defaults.d.ts","sourceRoot":"","sources":["../src/openapi-defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,CAAC,gBAuHhD,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default OpenAPI components provided by Glasswork.
|
|
3
|
+
* These include headers for features implemented by the framework:
|
|
4
|
+
* - Rate limiting headers
|
|
5
|
+
* - Pagination headers
|
|
6
|
+
* - CORS headers
|
|
7
|
+
* - Error response schemas
|
|
8
|
+
*/
|
|
9
|
+
export const defaultOpenAPIComponents = {
|
|
10
|
+
schemas: {
|
|
11
|
+
// Error response schemas
|
|
12
|
+
ErrorResponse: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
required: ['error'],
|
|
15
|
+
properties: {
|
|
16
|
+
error: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
maxLength: 1000,
|
|
19
|
+
pattern: '^[\\s\\S]*$', // Any characters including whitespace
|
|
20
|
+
description: 'Error message',
|
|
21
|
+
},
|
|
22
|
+
issues: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
maxItems: 100,
|
|
25
|
+
items: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
required: ['message'],
|
|
28
|
+
properties: {
|
|
29
|
+
message: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
maxLength: 500,
|
|
32
|
+
},
|
|
33
|
+
path: {
|
|
34
|
+
type: 'array',
|
|
35
|
+
maxItems: 20,
|
|
36
|
+
items: {
|
|
37
|
+
oneOf: [
|
|
38
|
+
{ type: 'string', maxLength: 200 },
|
|
39
|
+
{ type: 'number', format: 'int32' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
description: 'Validation error details',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
headers: {
|
|
51
|
+
// CORS headers
|
|
52
|
+
'Access-Control-Allow-Origin': {
|
|
53
|
+
description: 'Allowed origin for CORS requests',
|
|
54
|
+
schema: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
maxLength: 2048,
|
|
57
|
+
pattern: '^https?://[a-zA-Z0-9.-]+(:[0-9]+)?$',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
// Rate limiting headers (from rate-limit.ts)
|
|
61
|
+
'RateLimit-Limit': {
|
|
62
|
+
description: 'Total requests allowed in the current time window',
|
|
63
|
+
schema: {
|
|
64
|
+
type: 'integer',
|
|
65
|
+
format: 'int32',
|
|
66
|
+
minimum: 0,
|
|
67
|
+
maximum: 10000,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
'RateLimit-Remaining': {
|
|
71
|
+
description: 'Remaining requests in the current window',
|
|
72
|
+
schema: {
|
|
73
|
+
type: 'integer',
|
|
74
|
+
format: 'int32',
|
|
75
|
+
minimum: 0,
|
|
76
|
+
maximum: 10000,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
'RateLimit-Reset': {
|
|
80
|
+
description: 'Seconds until the rate limit window resets',
|
|
81
|
+
schema: {
|
|
82
|
+
type: 'integer',
|
|
83
|
+
format: 'int32',
|
|
84
|
+
minimum: 0,
|
|
85
|
+
maximum: 3600,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
'Retry-After': {
|
|
89
|
+
description: 'Seconds to wait before retrying the request (429 response)',
|
|
90
|
+
schema: {
|
|
91
|
+
type: 'integer',
|
|
92
|
+
format: 'int32',
|
|
93
|
+
minimum: 1,
|
|
94
|
+
maximum: 3600,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
// Pagination headers (from pagination.ts)
|
|
98
|
+
'X-Total-Count': {
|
|
99
|
+
description: 'Total number of items available',
|
|
100
|
+
schema: {
|
|
101
|
+
type: 'integer',
|
|
102
|
+
format: 'int32',
|
|
103
|
+
minimum: 0,
|
|
104
|
+
maximum: 2147483647, // Max int32
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
'X-Page': {
|
|
108
|
+
description: 'Current page number',
|
|
109
|
+
schema: {
|
|
110
|
+
type: 'integer',
|
|
111
|
+
format: 'int32',
|
|
112
|
+
minimum: 1,
|
|
113
|
+
maximum: 2147483647, // Max int32
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
'X-Limit': {
|
|
117
|
+
description: 'Number of items per page',
|
|
118
|
+
schema: {
|
|
119
|
+
type: 'integer',
|
|
120
|
+
format: 'int32',
|
|
121
|
+
minimum: 1,
|
|
122
|
+
maximum: 100,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=openapi-defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-defaults.js","sourceRoot":"","sources":["../src/openapi-defaults.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OpenAPIResponseHook } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a hook that adds CORS headers to responses
|
|
4
|
+
*/
|
|
5
|
+
export declare function createCorsHeadersHook(enabled: boolean): OpenAPIResponseHook;
|
|
6
|
+
/**
|
|
7
|
+
* Create a hook that adds rate limiting headers to responses
|
|
8
|
+
*/
|
|
9
|
+
export declare function createRateLimitHeadersHook(enabled: boolean): OpenAPIResponseHook;
|
|
10
|
+
/**
|
|
11
|
+
* Create a hook that adds pagination headers to 200 responses
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPaginationHeadersHook(): OpenAPIResponseHook;
|
|
14
|
+
//# sourceMappingURL=openapi-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-hooks.d.ts","sourceRoot":"","sources":["../src/openapi-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAc3E;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,CAgChF;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,mBAAmB,CAyBjE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a hook that adds CORS headers to responses
|
|
3
|
+
*/
|
|
4
|
+
export function createCorsHeadersHook(enabled) {
|
|
5
|
+
return (response, _context) => {
|
|
6
|
+
if (!enabled)
|
|
7
|
+
return response;
|
|
8
|
+
return {
|
|
9
|
+
...response,
|
|
10
|
+
headers: {
|
|
11
|
+
...response.headers,
|
|
12
|
+
'Access-Control-Allow-Origin': {
|
|
13
|
+
$ref: '#/components/headers/Access-Control-Allow-Origin',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a hook that adds rate limiting headers to responses
|
|
21
|
+
*/
|
|
22
|
+
export function createRateLimitHeadersHook(enabled) {
|
|
23
|
+
return (response, context) => {
|
|
24
|
+
if (!enabled)
|
|
25
|
+
return response;
|
|
26
|
+
const { statusCode } = context;
|
|
27
|
+
const headers = {
|
|
28
|
+
'RateLimit-Limit': {
|
|
29
|
+
$ref: '#/components/headers/RateLimit-Limit',
|
|
30
|
+
},
|
|
31
|
+
'RateLimit-Remaining': {
|
|
32
|
+
$ref: '#/components/headers/RateLimit-Remaining',
|
|
33
|
+
},
|
|
34
|
+
'RateLimit-Reset': {
|
|
35
|
+
$ref: '#/components/headers/RateLimit-Reset',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
// Add Retry-After header for 429 responses
|
|
39
|
+
if (statusCode === '429') {
|
|
40
|
+
headers['Retry-After'] = {
|
|
41
|
+
$ref: '#/components/headers/Retry-After',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...response,
|
|
46
|
+
headers: {
|
|
47
|
+
...response.headers,
|
|
48
|
+
...headers,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a hook that adds pagination headers to 200 responses
|
|
55
|
+
*/
|
|
56
|
+
export function createPaginationHeadersHook() {
|
|
57
|
+
return (response, context) => {
|
|
58
|
+
const { statusCode, routeConfig } = context;
|
|
59
|
+
// Only add pagination headers to 200 responses with paginate flag
|
|
60
|
+
if (statusCode !== '200' || !routeConfig.paginate) {
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...response,
|
|
65
|
+
headers: {
|
|
66
|
+
...response.headers,
|
|
67
|
+
'X-Total-Count': {
|
|
68
|
+
$ref: '#/components/headers/X-Total-Count',
|
|
69
|
+
},
|
|
70
|
+
'X-Page': {
|
|
71
|
+
$ref: '#/components/headers/X-Page',
|
|
72
|
+
},
|
|
73
|
+
'X-Limit': {
|
|
74
|
+
$ref: '#/components/headers/X-Limit',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=openapi-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-hooks.js","sourceRoot":"","sources":["../src/openapi-hooks.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAE9B,OAAO;YACL,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,6BAA6B,EAAE;oBAC7B,IAAI,EAAE,kDAAkD;iBACzD;aACF;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAgB;IACzD,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC3B,IAAI,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;QAC/B,MAAM,OAAO,GAAqC;YAChD,iBAAiB,EAAE;gBACjB,IAAI,EAAE,sCAAsC;aAC7C;YACD,qBAAqB,EAAE;gBACrB,IAAI,EAAE,0CAA0C;aACjD;YACD,iBAAiB,EAAE;gBACjB,IAAI,EAAE,sCAAsC;aAC7C;SACF,CAAC;QAEF,2CAA2C;QAC3C,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;YACzB,OAAO,CAAC,aAAa,CAAC,GAAG;gBACvB,IAAI,EAAE,kCAAkC;aACzC,CAAC;QACJ,CAAC;QAED,OAAO;YACL,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,GAAG,OAAO;aACX;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC3B,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAE5C,kEAAkE;QAClE,IAAI,UAAU,KAAK,KAAK,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;YAClD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,OAAO;YACL,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,eAAe,EAAE;oBACf,IAAI,EAAE,oCAAoC;iBAC3C;gBACD,QAAQ,EAAE;oBACR,IAAI,EAAE,6BAA6B;iBACpC;gBACD,SAAS,EAAE;oBACT,IAAI,EAAE,8BAA8B;iBACrC;aACF;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { Environment, MiddlewareOptions, OpenAPIOptions, RateLimitOptions } from './types.js';
|
|
3
|
+
export interface ConfigureOpenAPIOptions {
|
|
4
|
+
app: Hono;
|
|
5
|
+
environment: Environment;
|
|
6
|
+
openapi: OpenAPIOptions;
|
|
7
|
+
rateLimit?: RateLimitOptions;
|
|
8
|
+
middleware?: MiddlewareOptions;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Configure OpenAPI documentation for the application.
|
|
12
|
+
*
|
|
13
|
+
* Behavior by environment:
|
|
14
|
+
* - development: Serve specs and UI at /api and /api/openapi.json
|
|
15
|
+
* - production: Write spec to disk (openapi.json), no serving
|
|
16
|
+
* - test: Disabled by default
|
|
17
|
+
*
|
|
18
|
+
* This function automatically sets up response hooks based on enabled features
|
|
19
|
+
* and merges them with any application-provided hooks.
|
|
20
|
+
*
|
|
21
|
+
* @param options - Configuration options
|
|
22
|
+
*/
|
|
23
|
+
export declare function configureOpenAPI(options: ConfigureOpenAPIOptions): void;
|
|
24
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAUjC,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnG,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,IAAI,CAAC;IACV,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAgEvE"}
|
package/dist/openapi.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { swaggerUI } from '@hono/swagger-ui';
|
|
3
|
+
import { openAPISpecs } from 'hono-openapi';
|
|
4
|
+
import { defaultOpenAPIComponents } from './openapi-defaults.js';
|
|
5
|
+
import { createCorsHeadersHook, createPaginationHeadersHook, createRateLimitHeadersHook, } from './openapi-hooks.js';
|
|
6
|
+
import { setGlobalResponseHooks } from './route-helpers.js';
|
|
7
|
+
import { deepMerge } from './utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Configure OpenAPI documentation for the application.
|
|
10
|
+
*
|
|
11
|
+
* Behavior by environment:
|
|
12
|
+
* - development: Serve specs and UI at /api and /api/openapi.json
|
|
13
|
+
* - production: Write spec to disk (openapi.json), no serving
|
|
14
|
+
* - test: Disabled by default
|
|
15
|
+
*
|
|
16
|
+
* This function automatically sets up response hooks based on enabled features
|
|
17
|
+
* and merges them with any application-provided hooks.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Configuration options
|
|
20
|
+
*/
|
|
21
|
+
export function configureOpenAPI(options) {
|
|
22
|
+
const { app, environment, openapi, rateLimit, middleware } = options;
|
|
23
|
+
if (!openapi.enabled) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const shouldServeSpecs = openapi.serveSpecs ?? environment === 'development';
|
|
27
|
+
const shouldServeUI = openapi.serveUI ?? environment === 'development';
|
|
28
|
+
// Build response hooks based on enabled features
|
|
29
|
+
const frameworkHooks = [
|
|
30
|
+
createCorsHeadersHook(!!middleware?.cors),
|
|
31
|
+
createRateLimitHeadersHook(rateLimit?.enabled ?? false),
|
|
32
|
+
createPaginationHeadersHook(),
|
|
33
|
+
];
|
|
34
|
+
// Merge with application-provided hooks
|
|
35
|
+
const allHooks = [...frameworkHooks, ...(openapi.responseHooks || [])];
|
|
36
|
+
// Make hooks available to route definitions globally
|
|
37
|
+
// This must be done synchronously before routes are mounted
|
|
38
|
+
setGlobalResponseHooks(allHooks);
|
|
39
|
+
// Filter components based on enabled features (only include headers that are actually used)
|
|
40
|
+
const filteredComponents = filterComponentsByFeatures(defaultOpenAPIComponents, {
|
|
41
|
+
rateLimitEnabled: rateLimit?.enabled ?? false,
|
|
42
|
+
corsEnabled: !!middleware?.cors,
|
|
43
|
+
});
|
|
44
|
+
// Merge application documentation with filtered Glasswork defaults
|
|
45
|
+
const mergedDocumentation = deepMerge({ components: filteredComponents }, openapi.documentation || {});
|
|
46
|
+
// Serve OpenAPI spec endpoint (development only by default)
|
|
47
|
+
if (shouldServeSpecs) {
|
|
48
|
+
app.get('/api/openapi.json', openAPISpecs(app, { documentation: mergedDocumentation }));
|
|
49
|
+
}
|
|
50
|
+
// Serve Swagger UI (development only by default)
|
|
51
|
+
if (shouldServeUI) {
|
|
52
|
+
app.get('/api', swaggerUI({ url: '/api/openapi.json' }));
|
|
53
|
+
}
|
|
54
|
+
// Write OpenAPI spec to file (production/Lambda)
|
|
55
|
+
if (openapi.writeToFile) {
|
|
56
|
+
const filePath = openapi.writeToFile;
|
|
57
|
+
// Generate spec and write to file
|
|
58
|
+
// This happens after routes are registered
|
|
59
|
+
setTimeout(async () => {
|
|
60
|
+
try {
|
|
61
|
+
// Use Hono's built-in request method to simulate the request
|
|
62
|
+
const response = await app.request('/api/openapi.json');
|
|
63
|
+
const specContent = await response.text();
|
|
64
|
+
await writeFile(filePath, specContent, 'utf-8');
|
|
65
|
+
console.log(`[Glasswork] OpenAPI spec written to ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('[Glasswork] Failed to write OpenAPI spec:', error);
|
|
69
|
+
}
|
|
70
|
+
}, 1000); // Wait for routes to be registered
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Filter component headers based on enabled features.
|
|
75
|
+
* This removes header definitions from the components schema that won't be used.
|
|
76
|
+
*/
|
|
77
|
+
function filterComponentsByFeatures(components, features) {
|
|
78
|
+
if (!components.headers) {
|
|
79
|
+
return components;
|
|
80
|
+
}
|
|
81
|
+
const filteredHeaders = Object.fromEntries(Object.entries(components.headers).filter(([name]) => shouldIncludeHeader(name, features)));
|
|
82
|
+
return {
|
|
83
|
+
...components,
|
|
84
|
+
headers: filteredHeaders,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Determine if a header should be included based on feature configuration
|
|
89
|
+
*/
|
|
90
|
+
function shouldIncludeHeader(headerName, { rateLimitEnabled, corsEnabled }) {
|
|
91
|
+
// Always include pagination headers (they're route-specific, not feature-gated)
|
|
92
|
+
if (headerName.startsWith('X-')) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
// Include CORS headers only if CORS is enabled
|
|
96
|
+
if (headerName === 'AccessControlAllowOrigin') {
|
|
97
|
+
return corsEnabled;
|
|
98
|
+
}
|
|
99
|
+
// Include rate limit headers only if rate limiting is enabled
|
|
100
|
+
if (headerName.startsWith('RateLimit') || headerName === 'RetryAfter') {
|
|
101
|
+
return rateLimitEnabled;
|
|
102
|
+
}
|
|
103
|
+
// Include other headers by default
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
|