glasswork 0.6.1 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/openapi/defaults.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAO/C;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,CAAC,gBA8FhD,CAAC"}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/openapi/defaults.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAU/C;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,CAAC,gBA8FhD,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import { toJsonSchema } from '@valibot/to-json-schema';
2
2
  import { ErrorResponseDto, ValidationErrorResponseDto, ValidationIssueDto, } from '../http/error-response.dto.js';
3
+ /** Maximum value for a signed 32-bit integer */
4
+ const INT32_MAX = 2_147_483_647;
3
5
  /**
4
6
  * Default OpenAPI components provided by Glasswork.
5
7
  * These include headers for features implemented by the framework:
@@ -69,7 +71,7 @@ export const defaultOpenAPIComponents = {
69
71
  type: 'integer',
70
72
  format: 'int32',
71
73
  minimum: 0,
72
- maximum: 2147483647, // Max int32
74
+ maximum: INT32_MAX
73
75
  },
74
76
  },
75
77
  'X-Total-Pages': {
@@ -78,7 +80,7 @@ export const defaultOpenAPIComponents = {
78
80
  type: 'integer',
79
81
  format: 'int32',
80
82
  minimum: 0,
81
- maximum: 2147483647, // Max int32
83
+ maximum: INT32_MAX
82
84
  },
83
85
  },
84
86
  'X-Current-Page': {
@@ -87,7 +89,7 @@ export const defaultOpenAPIComponents = {
87
89
  type: 'integer',
88
90
  format: 'int32',
89
91
  minimum: 1,
90
- maximum: 2147483647, // Max int32
92
+ maximum: INT32_MAX
91
93
  },
92
94
  },
93
95
  'X-Page-Size': {
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/openapi/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAEvC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA+B;IAClE,OAAO,EAAE;QACP,uDAAuD;QACvD,aAAa,EAAE,YAAY,CAAC,gBAAgB,CAA2B;QACvE,uBAAuB,EAAE,YAAY,CAAC,0BAA0B,CAA2B;QAC3F,eAAe,EAAE,YAAY,CAAC,kBAAkB,CAA2B;KAC5E;IACD,OAAO,EAAE;QACP,eAAe;QACf,6BAA6B,EAAE;YAC7B,WAAW,EAAE,kCAAkC;YAC/C,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,IAAI;gBACf,OAAO,EAAE,qCAAqC;aAC/C;SACF;QAED,6CAA6C;QAC7C,iBAAiB,EAAE;YACjB,WAAW,EAAE,mDAAmD;YAChE,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,KAAK;aACf;SACF;QACD,qBAAqB,EAAE;YACrB,WAAW,EAAE,0CAA0C;YACvD,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,KAAK;aACf;SACF;QACD,iBAAiB,EAAE;YACjB,WAAW,EAAE,4CAA4C;YACzD,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,IAAI;aACd;SACF;QACD,aAAa,EAAE;YACb,WAAW,EAAE,4DAA4D;YACzE,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,IAAI;aACd;SACF;QAED,uCAAuC;QACvC,eAAe,EAAE;YACf,WAAW,EAAE,iCAAiC;YAC9C,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,UAAU,EAAE,YAAY;aAClC;SACF;QACD,eAAe,EAAE;YACf,WAAW,EAAE,uBAAuB;YACpC,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,UAAU,EAAE,YAAY;aAClC;SACF;QACD,gBAAgB,EAAE;YAChB,WAAW,EAAE,qBAAqB;YAClC,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,UAAU,EAAE,YAAY;aAClC;SACF;QACD,aAAa,EAAE;YACb,WAAW,EAAE,0BAA0B;YACvC,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,GAAG;aACb;SACF;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/openapi/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAEvC,gDAAgD;AAChD,MAAM,SAAS,GAAG,aAAa,CAAC;AAEhC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA+B;IAClE,OAAO,EAAE;QACP,uDAAuD;QACvD,aAAa,EAAE,YAAY,CAAC,gBAAgB,CAA2B;QACvE,uBAAuB,EAAE,YAAY,CAAC,0BAA0B,CAA2B;QAC3F,eAAe,EAAE,YAAY,CAAC,kBAAkB,CAA2B;KAC5E;IACD,OAAO,EAAE;QACP,eAAe;QACf,6BAA6B,EAAE;YAC7B,WAAW,EAAE,kCAAkC;YAC/C,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,IAAI;gBACf,OAAO,EAAE,qCAAqC;aAC/C;SACF;QAED,6CAA6C;QAC7C,iBAAiB,EAAE;YACjB,WAAW,EAAE,mDAAmD;YAChE,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,KAAK;aACf;SACF;QACD,qBAAqB,EAAE;YACrB,WAAW,EAAE,0CAA0C;YACvD,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,KAAK;aACf;SACF;QACD,iBAAiB,EAAE;YACjB,WAAW,EAAE,4CAA4C;YACzD,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,IAAI;aACd;SACF;QACD,aAAa,EAAE;YACb,WAAW,EAAE,4DAA4D;YACzE,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,IAAI;aACd;SACF;QAED,uCAAuC;QACvC,eAAe,EAAE;YACf,WAAW,EAAE,iCAAiC;YAC9C,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,SAAS;aACnB;SACF;QACD,eAAe,EAAE;YACf,WAAW,EAAE,uBAAuB;YACpC,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,SAAS;aACnB;SACF;QACD,gBAAgB,EAAE;YAChB,WAAW,EAAE,qBAAqB;YAClC,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,SAAS;aACnB;SACF;QACD,aAAa,EAAE;YACb,WAAW,EAAE,0BAA0B;YACvC,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,CAAC;gBACV,OAAO,EAAE,GAAG;aACb;SACF;KACF;CACF,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { OpenAPIProcessorContext, OpenAPIResponseObject, OpenAPIResponseProcessor } from '../core/types.js';
2
+ /**
3
+ * Create a processor that adds CORS headers to responses.
4
+ * Auto-enabled when middleware.cors is configured.
5
+ */
6
+ export declare function createCorsHeadersProcessor(enabled: boolean): OpenAPIResponseProcessor;
7
+ /**
8
+ * Create a processor that adds rate limiting headers to responses.
9
+ * Auto-enabled when rateLimit.enabled is true.
10
+ */
11
+ export declare function createRateLimitHeadersProcessor(enabled: boolean): OpenAPIResponseProcessor;
12
+ /**
13
+ * Processor that adds pagination headers to 200 responses.
14
+ * Auto-enabled when the route query schema includes pagination fields (page, pageSize).
15
+ */
16
+ export declare const paginationHeadersProcessor: OpenAPIResponseProcessor;
17
+ /**
18
+ * Processor that adds response headers defined in route config.
19
+ * Supports both simple array format and flexible status-code-specific format.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Simple: applies to all responses
24
+ * openapi: { responseHeaders: ['Api-Version', 'Server-Timing'] }
25
+ *
26
+ * // Flexible: status-code specific
27
+ * openapi: {
28
+ * responseHeaders: {
29
+ * '200': ['X-Total-Count'],
30
+ * 'default': ['Api-Version']
31
+ * }
32
+ * }
33
+ * ```
34
+ */
35
+ export declare const responseHeadersProcessor: OpenAPIResponseProcessor;
36
+ /**
37
+ * Create built-in processors based on bootstrap configuration.
38
+ *
39
+ * @internal
40
+ */
41
+ export declare function createBuiltinProcessors(config: {
42
+ corsEnabled: boolean;
43
+ rateLimitEnabled: boolean;
44
+ }): OpenAPIResponseProcessor[];
45
+ /**
46
+ * Apply all processors to a response object.
47
+ *
48
+ * @internal
49
+ */
50
+ export declare function applyProcessors(response: OpenAPIResponseObject, context: OpenAPIProcessorContext, processors: OpenAPIResponseProcessor[]): OpenAPIResponseObject;
51
+ //# sourceMappingURL=openapi-processors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-processors.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi-processors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,kBAAkB,CAAC;AAE1B;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,wBAAwB,CAcrF;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,OAAO,GAAG,wBAAwB,CAgC1F;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,wBA0BxC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,wBAAwB,EAAE,wBA4CtC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,GAAG,wBAAwB,EAAE,CAO7B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,wBAAwB,EAAE,GACrC,qBAAqB,CAMvB"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Create a processor that adds CORS headers to responses.
3
+ * Auto-enabled when middleware.cors is configured.
4
+ */
5
+ export function createCorsHeadersProcessor(enabled) {
6
+ return (response, _context) => {
7
+ if (!enabled)
8
+ return response;
9
+ return {
10
+ ...response,
11
+ headers: {
12
+ ...response.headers,
13
+ 'Access-Control-Allow-Origin': {
14
+ $ref: '#/components/headers/Access-Control-Allow-Origin',
15
+ },
16
+ },
17
+ };
18
+ };
19
+ }
20
+ /**
21
+ * Create a processor that adds rate limiting headers to responses.
22
+ * Auto-enabled when rateLimit.enabled is true.
23
+ */
24
+ export function createRateLimitHeadersProcessor(enabled) {
25
+ return (response, context) => {
26
+ if (!enabled)
27
+ return response;
28
+ const { statusCode } = context;
29
+ const headers = {
30
+ 'RateLimit-Limit': {
31
+ $ref: '#/components/headers/RateLimit-Limit',
32
+ },
33
+ 'RateLimit-Remaining': {
34
+ $ref: '#/components/headers/RateLimit-Remaining',
35
+ },
36
+ 'RateLimit-Reset': {
37
+ $ref: '#/components/headers/RateLimit-Reset',
38
+ },
39
+ };
40
+ // Add Retry-After header for 429 responses
41
+ if (statusCode === '429') {
42
+ headers['Retry-After'] = {
43
+ $ref: '#/components/headers/Retry-After',
44
+ };
45
+ }
46
+ return {
47
+ ...response,
48
+ headers: {
49
+ ...response.headers,
50
+ ...headers,
51
+ },
52
+ };
53
+ };
54
+ }
55
+ /**
56
+ * Processor that adds pagination headers to 200 responses.
57
+ * Auto-enabled when the route query schema includes pagination fields (page, pageSize).
58
+ */
59
+ export const paginationHeadersProcessor = (response, context) => {
60
+ const { statusCode, hasPagination } = context;
61
+ // Only add pagination headers to 200 responses with pagination
62
+ if (statusCode !== '200' || !hasPagination) {
63
+ return response;
64
+ }
65
+ return {
66
+ ...response,
67
+ headers: {
68
+ ...response.headers,
69
+ 'X-Total-Count': {
70
+ $ref: '#/components/headers/X-Total-Count',
71
+ },
72
+ 'X-Total-Pages': {
73
+ $ref: '#/components/headers/X-Total-Pages',
74
+ },
75
+ 'X-Current-Page': {
76
+ $ref: '#/components/headers/X-Current-Page',
77
+ },
78
+ 'X-Page-Size': {
79
+ $ref: '#/components/headers/X-Page-Size',
80
+ },
81
+ },
82
+ };
83
+ };
84
+ /**
85
+ * Processor that adds response headers defined in route config.
86
+ * Supports both simple array format and flexible status-code-specific format.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * // Simple: applies to all responses
91
+ * openapi: { responseHeaders: ['Api-Version', 'Server-Timing'] }
92
+ *
93
+ * // Flexible: status-code specific
94
+ * openapi: {
95
+ * responseHeaders: {
96
+ * '200': ['X-Total-Count'],
97
+ * 'default': ['Api-Version']
98
+ * }
99
+ * }
100
+ * ```
101
+ */
102
+ export const responseHeadersProcessor = (response, context) => {
103
+ const { statusCode, routeConfig } = context;
104
+ const responseHeaders = routeConfig.openapi?.responseHeaders;
105
+ if (!responseHeaders) {
106
+ return response;
107
+ }
108
+ let headersToAdd = [];
109
+ if (Array.isArray(responseHeaders)) {
110
+ // Simple format: applies to all responses
111
+ headersToAdd = responseHeaders;
112
+ }
113
+ else {
114
+ // Flexible format: status-code specific
115
+ const statusSpecific = responseHeaders[statusCode];
116
+ const defaultHeaders = responseHeaders['default'];
117
+ if (statusSpecific) {
118
+ headersToAdd = [...headersToAdd, ...statusSpecific];
119
+ }
120
+ if (defaultHeaders) {
121
+ headersToAdd = [...headersToAdd, ...defaultHeaders];
122
+ }
123
+ }
124
+ if (headersToAdd.length === 0) {
125
+ return response;
126
+ }
127
+ const newHeaders = {};
128
+ for (const header of headersToAdd) {
129
+ newHeaders[header] = {
130
+ $ref: `#/components/headers/${header}`,
131
+ };
132
+ }
133
+ return {
134
+ ...response,
135
+ headers: {
136
+ ...response.headers,
137
+ ...newHeaders,
138
+ },
139
+ };
140
+ };
141
+ /**
142
+ * Create built-in processors based on bootstrap configuration.
143
+ *
144
+ * @internal
145
+ */
146
+ export function createBuiltinProcessors(config) {
147
+ return [
148
+ createCorsHeadersProcessor(config.corsEnabled),
149
+ createRateLimitHeadersProcessor(config.rateLimitEnabled),
150
+ paginationHeadersProcessor,
151
+ responseHeadersProcessor,
152
+ ];
153
+ }
154
+ /**
155
+ * Apply all processors to a response object.
156
+ *
157
+ * @internal
158
+ */
159
+ export function applyProcessors(response, context, processors) {
160
+ let result = response;
161
+ for (const processor of processors) {
162
+ result = processor(result, context);
163
+ }
164
+ return result;
165
+ }
166
+ //# sourceMappingURL=openapi-processors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-processors.js","sourceRoot":"","sources":["../../src/openapi/openapi-processors.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,27 @@ export interface ConfigureOpenAPIOptions {
7
7
  rateLimit?: RateLimitOptions;
8
8
  middleware?: MiddlewareOptions;
9
9
  }
10
+ /**
11
+ * Result from configuring OpenAPI, includes optional write function.
12
+ */
13
+ export interface ConfigureOpenAPIResult {
14
+ /**
15
+ * Write the OpenAPI spec to a file.
16
+ * Call this after all routes have been registered.
17
+ *
18
+ * @returns Promise that resolves when the file has been written
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const app = await bootstrap(config);
23
+ * const { writeSpec } = configureOpenAPI({ app, ... });
24
+ *
25
+ * // After all routes are registered:
26
+ * await writeSpec?.();
27
+ * ```
28
+ */
29
+ writeSpec?: () => Promise<void>;
30
+ }
10
31
  /**
11
32
  * Configure OpenAPI documentation for the application.
12
33
  *
@@ -15,10 +36,22 @@ export interface ConfigureOpenAPIOptions {
15
36
  * - production: No serving by default
16
37
  * - test: Disabled by default
17
38
  *
18
- * This function automatically sets up response hooks based on enabled features
19
- * and merges them with any application-provided hooks.
39
+ * Note: Response processors are now configured in bootstrap.ts and stored
40
+ * per-app instance via setOpenAPIContext. This eliminates global state.
20
41
  *
21
42
  * @param options - Configuration options
43
+ * @returns Object with optional `writeSpec` function for writing the spec to file
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Basic usage (auto-write with delay)
48
+ * configureOpenAPI({ app, environment, openapi });
49
+ *
50
+ * // Explicit write after routes are registered (recommended)
51
+ * const { writeSpec } = configureOpenAPI({ app, environment, openapi });
52
+ * // ... register all routes ...
53
+ * await writeSpec?.();
54
+ * ```
22
55
  */
23
- export declare function configureOpenAPI(options: ConfigureOpenAPIOptions): void;
56
+ export declare function configureOpenAPI(options: ConfigureOpenAPIOptions): ConfigureOpenAPIResult;
24
57
  //# sourceMappingURL=openapi.d.ts.map
@@ -1 +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,CAyFvE"}
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;AAQ1B,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;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CA2EzF"}
@@ -1,11 +1,11 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { swaggerUI } from '@hono/swagger-ui';
3
3
  import { openAPIRouteHandler } from 'hono-openapi';
4
- import { setGlobalResponseHooks, setGlobalSecuritySchemes } from '../http/route-helpers.js';
5
4
  import { deepMerge } from '../utils/deep-merge.js';
6
5
  import { createLogger } from '../utils/logger.js';
7
6
  import { defaultOpenAPIComponents } from './defaults.js';
8
- import { createCorsHeadersHook, createPaginationHeadersHook, createRateLimitHeadersHook, } from './hooks.js';
7
+ /** Default delay before writing OpenAPI spec (ms). Allows routes to register. */
8
+ const DEFAULT_WRITE_DELAY_MS = 1000;
9
9
  /**
10
10
  * Configure OpenAPI documentation for the application.
11
11
  *
@@ -14,30 +14,31 @@ import { createCorsHeadersHook, createPaginationHeadersHook, createRateLimitHead
14
14
  * - production: No serving by default
15
15
  * - test: Disabled by default
16
16
  *
17
- * This function automatically sets up response hooks based on enabled features
18
- * and merges them with any application-provided hooks.
17
+ * Note: Response processors are now configured in bootstrap.ts and stored
18
+ * per-app instance via setOpenAPIContext. This eliminates global state.
19
19
  *
20
20
  * @param options - Configuration options
21
+ * @returns Object with optional `writeSpec` function for writing the spec to file
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Basic usage (auto-write with delay)
26
+ * configureOpenAPI({ app, environment, openapi });
27
+ *
28
+ * // Explicit write after routes are registered (recommended)
29
+ * const { writeSpec } = configureOpenAPI({ app, environment, openapi });
30
+ * // ... register all routes ...
31
+ * await writeSpec?.();
32
+ * ```
21
33
  */
22
34
  export function configureOpenAPI(options) {
23
35
  const { app, environment, openapi, rateLimit, middleware } = options;
24
36
  if (!openapi.enabled) {
25
- return;
37
+ return {};
26
38
  }
27
39
  const logger = createLogger('Glasswork:OpenAPI', true);
28
40
  const shouldServeSpecs = openapi.serveSpecs ?? environment === 'development';
29
41
  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
42
  // Filter components based on enabled features (only include headers that are actually used)
42
43
  const filteredComponents = filterComponentsByFeatures(defaultOpenAPIComponents, {
43
44
  rateLimitEnabled: rateLimit?.enabled ?? false,
@@ -45,13 +46,6 @@ export function configureOpenAPI(options) {
45
46
  });
46
47
  // Merge application documentation with filtered Glasswork defaults
47
48
  const mergedDocumentation = deepMerge({ components: filteredComponents }, openapi.documentation || {});
48
- // Extract security scheme names from the merged documentation and make them available globally
49
- // This allows route definitions to reference the actual security schemes defined in the app
50
- const securitySchemes = extractSecuritySchemeNames(mergedDocumentation);
51
- setGlobalSecuritySchemes(securitySchemes);
52
- if (securitySchemes.length > 0) {
53
- logger.info(`Security schemes: ${securitySchemes.join(', ')}`);
54
- }
55
49
  // Serve OpenAPI spec endpoint (development only by default)
56
50
  if (shouldServeSpecs) {
57
51
  app.get('/api/openapi.json', openAPIRouteHandler(app, { documentation: mergedDocumentation }));
@@ -60,12 +54,11 @@ export function configureOpenAPI(options) {
60
54
  if (shouldServeUI) {
61
55
  app.get('/api', swaggerUI({ url: '/api/openapi.json' }));
62
56
  }
63
- // Write OpenAPI spec to file (optional)
57
+ // Create writeSpec function if file writing is configured
58
+ let writeSpec;
64
59
  if (openapi.writeToFile) {
65
60
  const filePath = openapi.writeToFile;
66
- // Generate spec and write to file
67
- // This happens after routes are registered
68
- setTimeout(async () => {
61
+ writeSpec = async () => {
69
62
  try {
70
63
  let specContent;
71
64
  if (shouldServeSpecs) {
@@ -87,19 +80,14 @@ export function configureOpenAPI(options) {
87
80
  catch (error) {
88
81
  logger.error('Failed to write OpenAPI spec:', error);
89
82
  }
90
- }, 1000); // Wait for routes to be registered
91
- }
92
- }
93
- /**
94
- * Extract security scheme names from OpenAPI documentation.
95
- * Returns an array of scheme names that can be used in route security definitions.
96
- */
97
- function extractSecuritySchemeNames(documentation) {
98
- const securitySchemes = documentation.components?.securitySchemes;
99
- if (!securitySchemes || typeof securitySchemes !== 'object') {
100
- return [];
83
+ };
84
+ // Auto-write with delay for backward compatibility
85
+ // NOTE: For more reliable writes, call writeSpec() explicitly after registering routes
86
+ setTimeout(() => {
87
+ writeSpec?.();
88
+ }, DEFAULT_WRITE_DELAY_MS);
101
89
  }
102
- return Object.keys(securitySchemes);
90
+ return { writeSpec };
103
91
  }
104
92
  /**
105
93
  * Filter component headers based on enabled features.
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/openapi/openapi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/openapi/openapi.ts"],"names":[],"mappings":"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"}
@@ -110,31 +110,57 @@ export declare function serializePrismaTypes<T>(data: T, config?: SerializationC
110
110
  * Prisma's Decimal has constructor.name as string (not literal 'Decimal'),
111
111
  * so we match on the toNumber() method which is the key interface we need.
112
112
  */
113
- type PrismaDecimalLike = {
113
+ export type PrismaDecimalLike = {
114
114
  toNumber(): number;
115
115
  constructor: {
116
116
  name: string;
117
117
  };
118
118
  };
119
+ /**
120
+ * Common date field naming patterns used in databases.
121
+ * Fields matching these patterns will accept Date objects in AcceptPrismaTypes.
122
+ *
123
+ * Patterns:
124
+ * - `${string}At` - createdAt, updatedAt, deletedAt, expiresAt, etc.
125
+ * - `${string}Date` - birthDate, startDate, endDate, effectiveDate, etc.
126
+ * - `${string}Time` - startTime, endTime (when stored as Date/DateTime)
127
+ * - `${string}Timestamp` - loginTimestamp, etc.
128
+ * - Common standalone names: 'date', 'timestamp', 'datetime'
129
+ */
130
+ type DateFieldPattern = `${string}At` | `${string}Date` | `${string}Time` | `${string}Timestamp` | 'date' | 'timestamp' | 'datetime';
119
131
  /**
120
132
  * Reverse type helper that accepts both serialized types AND their Prisma equivalents.
121
133
  * This allows handlers to return Prisma objects directly while TypeScript knows they'll be serialized.
122
134
  *
123
135
  * **How it works:**
124
- * - string → string | Date (accepts both)
125
- * - number → number | Decimal-like (accepts both)
126
- * - Recursively applies to objects and arrays
127
- *
128
- * ⚠️ **Important Trade-off:**
129
- * This type will accept Date for ANY string field and Decimal for ANY number field in your response.
130
- * While this may seem overly permissive, it's a pragmatic trade-off that:
131
- * - ✅ Enables seamless Prisma type serialization without manual type assertions
132
- * - ✅ Maintains structural type safety (correct shape, correct property names)
133
- * - ✅ Relies on schema validation for format correctness (which you should have anyway)
134
- * - ⚠️ Allows theoretically incorrect assignments (e.g., Date to a 'name' field)
135
- *
136
- * In practice, you're unlikely to accidentally pass `new Date()` to a name field, and if you do,
137
- * Valibot validation will likely catch it at runtime.
136
+ * - For Date fields: Only string fields with conventional date names (ending in `At`, `Date`, `Time`, etc.)
137
+ * accept Date objects. Other string fields only accept strings.
138
+ * - For Decimal fields: All number fields accept Decimal-like objects (see rationale below).
139
+ * - Recursively applies to objects and arrays.
140
+ *
141
+ * **Date Field Detection:**
142
+ * Date fields are detected by naming convention. Fields ending in `At`, `Date`, `Time`, `Timestamp`,
143
+ * or named `date`/`timestamp`/`datetime` will accept Date objects.
144
+ *
145
+ * If you have unconventionally named date fields, you have two options:
146
+ * 1. Rename them to follow the convention (recommended)
147
+ * 2. Use `strictTypes: true` in your route and handle types manually
148
+ * 3. Provide additional patterns via the `AdditionalDatePatterns` type parameter
149
+ *
150
+ * **Why Decimal is still permissive:**
151
+ * Unlike date fields which follow predictable naming conventions (`createdAt`, `updatedAt`, etc.),
152
+ * Decimal fields have domain-specific names with no reliable pattern:
153
+ * - `price`, `amount`, `balance` (finance)
154
+ * - `density`, `thickness`, `weight` (physics/materials)
155
+ * - `discountPercentage`, `taxRate` (percentages)
156
+ * - `latitude`, `longitude` (coordinates)
157
+ *
158
+ * Since there's no reliable naming convention for Decimal fields, we keep them permissive.
159
+ * In practice, developers rarely pass Decimal objects to non-decimal fields accidentally,
160
+ * and runtime schema validation will catch any actual errors.
161
+ *
162
+ * @template T - The serialized type to transform
163
+ * @template AdditionalDatePatterns - Optional additional patterns to match for date fields
138
164
  *
139
165
  * @example
140
166
  * ```typescript
@@ -143,44 +169,51 @@ type PrismaDecimalLike = {
143
169
  * id: v.string(),
144
170
  * name: v.string(),
145
171
  * createdAt: v.string(), // Expects ISO string
146
- * balance: v.number(), // Expects number
172
+ * balance: v.number(), // Expects number
147
173
  * });
148
174
  *
149
175
  * // ✅ Intended usage: Prisma types are automatically handled
150
176
  * handler: async () => {
151
177
  * const user = await prisma.user.findUnique({ where: { id } });
152
- * // user.createdAt is Date, user.balance is Decimal
178
+ * // user.createdAt is Date (matches 'At' pattern), user.balance is Decimal
153
179
  * return user; // Works! Automatically serialized to match schema
154
180
  * }
155
181
  *
156
- * // ✅ Also works: Pre-serialized or mixed data
182
+ * // ✅ Date fields with conventional names accept Date objects
157
183
  * handler: async () => {
158
184
  * return {
159
185
  * id: '123',
160
186
  * name: 'John',
161
- * createdAt: new Date(), // Date will be serialized
162
- * balance: new Decimal('100.50'), // Decimal will be serialized
187
+ * createdAt: new Date(), // ✅ 'createdAt' matches 'At' pattern
188
+ * birthDate: new Date(), // ✅ 'birthDate' matches 'Date' pattern
189
+ * balance: new Decimal('100.50'), // ✅ Decimal accepted for any number field
163
190
  * };
164
191
  * }
165
192
  *
166
- * // ⚠️ Type system allows this (but you probably won't write this accidentally)
193
+ * // ❌ Non-date string fields no longer accept Date (compile-time error)
167
194
  * handler: async () => {
168
195
  * return {
169
- * id: '123',
170
- * name: new Date(), // Type allows, but weird - who would do this?
196
+ * name: new Date(), // ❌ Type error! 'name' doesn't match date patterns
171
197
  * createdAt: '2025-01-01',
172
- * balance: 100,
173
198
  * };
174
199
  * }
175
- * // Note: Even if you did, schema validation might catch format issues
176
200
  *
177
- * // Type inference example
178
- * type ResponseType = AcceptPrismaTypes<{ createdAt: string; amount: number }>;
179
- * // Result: { createdAt: string | Date; amount: number | Decimal }
201
+ * // Using additional date patterns for unconventional field names
202
+ * type MyResponse = AcceptPrismaTypes<ResponseType, 'lastLogin' | 'nextRenewal'>;
180
203
  * ```
181
204
  */
182
- export type AcceptPrismaTypes<T> = T extends string ? string | Date : T extends number ? number | PrismaDecimalLike : T extends Array<infer U> ? Array<AcceptPrismaTypes<U>> : T extends object ? {
183
- [K in keyof T]: AcceptPrismaTypes<T[K]>;
205
+ /**
206
+ * Helper type to add Date support to string types (including nullable strings).
207
+ * Preserves null and undefined in unions.
208
+ */
209
+ type WithDateSupport<T> = T extends string | infer Rest ? string | Date | Rest : T;
210
+ /**
211
+ * Helper type to add Decimal support to number types (including nullable numbers).
212
+ * Preserves null and undefined in unions.
213
+ */
214
+ type WithDecimalSupport<T> = T extends number | infer Rest ? number | PrismaDecimalLike | Rest : T;
215
+ export type AcceptPrismaTypes<T, AdditionalDatePatterns extends string = never> = T extends object ? T extends Array<infer U> ? Array<AcceptPrismaTypes<U, AdditionalDatePatterns>> : {
216
+ [K in keyof T]: K extends string ? K extends DateFieldPattern | AdditionalDatePatterns ? T[K] extends string | null | undefined ? WithDateSupport<T[K]> : AcceptPrismaTypes<T[K], AdditionalDatePatterns> : T[K] extends number | null | undefined ? WithDecimalSupport<T[K]> : AcceptPrismaTypes<T[K], AdditionalDatePatterns> : AcceptPrismaTypes<T[K], AdditionalDatePatterns>;
184
217
  } : T;
185
218
  export {};
186
219
  //# sourceMappingURL=serialize-prisma-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serialize-prisma-types.d.ts","sourceRoot":"","sources":["../../src/utils/serialize-prisma-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GAC3C,MAAM,GACN,CAAC,SAAS;IAAE,WAAW,EAAE;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,CAAA;CAAE,GAC5C,MAAM,GACN,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GACtB,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,GACzB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACzC,CAAC,CAAC;AAEZ;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,YAAY,EAAE,eAAe,EAAE,CAAC;CACjC;AAsCD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,aAAa,EAAE,mBAE3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,CAAC,EACP,MAAM,GAAE,mBAAmC,EAC3C,MAAM,SAAI,EACV,KAAK,kBAAwB,GAC5B,eAAe,CAAC,CAAC,CAAC,CAqDpB;AAED;;;;;GAKG;AACH,KAAK,iBAAiB,GAAG;IACvB,QAAQ,IAAI,MAAM,CAAC;IACnB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC/C,MAAM,GAAG,IAAI,GACb,CAAC,SAAS,MAAM,GACd,MAAM,GAAG,iBAAiB,GAC1B,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GACtB,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAC3B,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAC3C,CAAC,CAAC"}
1
+ {"version":3,"file":"serialize-prisma-types.d.ts","sourceRoot":"","sources":["../../src/utils/serialize-prisma-types.ts"],"names":[],"mappings":"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"}