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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/dist/bootstrap.d.ts +55 -0
  4. package/dist/bootstrap.d.ts.map +1 -0
  5. package/dist/bootstrap.js +359 -0
  6. package/dist/bootstrap.js.map +1 -0
  7. package/dist/core/bootstrap.d.ts +55 -0
  8. package/dist/core/bootstrap.d.ts.map +1 -0
  9. package/dist/core/bootstrap.js +329 -0
  10. package/dist/core/bootstrap.js.map +1 -0
  11. package/dist/core/module.d.ts +21 -0
  12. package/dist/core/module.d.ts.map +1 -0
  13. package/dist/core/module.js +85 -0
  14. package/dist/core/module.js.map +1 -0
  15. package/dist/core/types.d.ts +273 -0
  16. package/dist/core/types.d.ts.map +1 -0
  17. package/dist/core/types.js +2 -0
  18. package/dist/core/types.js.map +1 -0
  19. package/dist/environment.d.ts +21 -0
  20. package/dist/environment.d.ts.map +1 -0
  21. package/dist/environment.js +29 -0
  22. package/dist/environment.js.map +1 -0
  23. package/dist/error-handler.d.ts +33 -0
  24. package/dist/error-handler.d.ts.map +1 -0
  25. package/dist/error-handler.js +53 -0
  26. package/dist/error-handler.js.map +1 -0
  27. package/dist/errors.d.ts +36 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +65 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/get-client-ip.d.ts +24 -0
  32. package/dist/get-client-ip.d.ts.map +1 -0
  33. package/dist/get-client-ip.js +49 -0
  34. package/dist/get-client-ip.js.map +1 -0
  35. package/dist/http/error-handler.d.ts +33 -0
  36. package/dist/http/error-handler.d.ts.map +1 -0
  37. package/dist/http/error-handler.js +55 -0
  38. package/dist/http/error-handler.js.map +1 -0
  39. package/dist/http/errors.d.ts +36 -0
  40. package/dist/http/errors.d.ts.map +1 -0
  41. package/dist/http/errors.js +65 -0
  42. package/dist/http/errors.js.map +1 -0
  43. package/dist/http/route-helpers.d.ts +172 -0
  44. package/dist/http/route-helpers.d.ts.map +1 -0
  45. package/dist/http/route-helpers.js +249 -0
  46. package/dist/http/route-helpers.js.map +1 -0
  47. package/dist/index.d.ts +33 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +29 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/logger.d.ts +37 -0
  52. package/dist/logger.d.ts.map +1 -0
  53. package/dist/logger.js +52 -0
  54. package/dist/logger.js.map +1 -0
  55. package/dist/middleware/rate-limit.d.ts +7 -0
  56. package/dist/middleware/rate-limit.d.ts.map +1 -0
  57. package/dist/middleware/rate-limit.js +182 -0
  58. package/dist/middleware/rate-limit.js.map +1 -0
  59. package/dist/module.d.ts +21 -0
  60. package/dist/module.d.ts.map +1 -0
  61. package/dist/module.js +85 -0
  62. package/dist/module.js.map +1 -0
  63. package/dist/omit.d.ts +20 -0
  64. package/dist/omit.d.ts.map +1 -0
  65. package/dist/omit.js +28 -0
  66. package/dist/omit.js.map +1 -0
  67. package/dist/openapi/defaults.d.ts +11 -0
  68. package/dist/openapi/defaults.d.ts.map +1 -0
  69. package/dist/openapi/defaults.js +127 -0
  70. package/dist/openapi/defaults.js.map +1 -0
  71. package/dist/openapi/hooks.d.ts +14 -0
  72. package/dist/openapi/hooks.d.ts.map +1 -0
  73. package/dist/openapi/hooks.js +80 -0
  74. package/dist/openapi/hooks.js.map +1 -0
  75. package/dist/openapi/openapi.d.ts +24 -0
  76. package/dist/openapi/openapi.d.ts.map +1 -0
  77. package/dist/openapi/openapi.js +108 -0
  78. package/dist/openapi/openapi.js.map +1 -0
  79. package/dist/openapi-defaults.d.ts +11 -0
  80. package/dist/openapi-defaults.d.ts.map +1 -0
  81. package/dist/openapi-defaults.js +127 -0
  82. package/dist/openapi-defaults.js.map +1 -0
  83. package/dist/openapi-hooks.d.ts +14 -0
  84. package/dist/openapi-hooks.d.ts.map +1 -0
  85. package/dist/openapi-hooks.js +80 -0
  86. package/dist/openapi-hooks.js.map +1 -0
  87. package/dist/openapi.d.ts +24 -0
  88. package/dist/openapi.d.ts.map +1 -0
  89. package/dist/openapi.js +106 -0
  90. package/dist/openapi.js.map +1 -0
  91. package/dist/pagination.d.ts +48 -0
  92. package/dist/pagination.d.ts.map +1 -0
  93. package/dist/pagination.js +43 -0
  94. package/dist/pagination.js.map +1 -0
  95. package/dist/pick.d.ts +20 -0
  96. package/dist/pick.d.ts.map +1 -0
  97. package/dist/pick.js +28 -0
  98. package/dist/pick.js.map +1 -0
  99. package/dist/rate-limit.d.ts +7 -0
  100. package/dist/rate-limit.d.ts.map +1 -0
  101. package/dist/rate-limit.js +180 -0
  102. package/dist/rate-limit.js.map +1 -0
  103. package/dist/route-helpers.d.ts +172 -0
  104. package/dist/route-helpers.d.ts.map +1 -0
  105. package/dist/route-helpers.js +249 -0
  106. package/dist/route-helpers.js.map +1 -0
  107. package/dist/serialize-dates.d.ts +27 -0
  108. package/dist/serialize-dates.d.ts.map +1 -0
  109. package/dist/serialize-dates.js +39 -0
  110. package/dist/serialize-dates.js.map +1 -0
  111. package/dist/testing.d.ts +87 -0
  112. package/dist/testing.d.ts.map +1 -0
  113. package/dist/testing.js +94 -0
  114. package/dist/testing.js.map +1 -0
  115. package/dist/types.d.ts +273 -0
  116. package/dist/types.d.ts.map +1 -0
  117. package/dist/types.js +2 -0
  118. package/dist/types.js.map +1 -0
  119. package/dist/utils/deep-merge.d.ts +5 -0
  120. package/dist/utils/deep-merge.d.ts.map +1 -0
  121. package/dist/utils/deep-merge.js +21 -0
  122. package/dist/utils/deep-merge.js.map +1 -0
  123. package/dist/utils/environment.d.ts +21 -0
  124. package/dist/utils/environment.d.ts.map +1 -0
  125. package/dist/utils/environment.js +29 -0
  126. package/dist/utils/environment.js.map +1 -0
  127. package/dist/utils/get-client-ip.d.ts +24 -0
  128. package/dist/utils/get-client-ip.d.ts.map +1 -0
  129. package/dist/utils/get-client-ip.js +49 -0
  130. package/dist/utils/get-client-ip.js.map +1 -0
  131. package/dist/utils/logger.d.ts +37 -0
  132. package/dist/utils/logger.d.ts.map +1 -0
  133. package/dist/utils/logger.js +52 -0
  134. package/dist/utils/logger.js.map +1 -0
  135. package/dist/utils/omit.d.ts +20 -0
  136. package/dist/utils/omit.d.ts.map +1 -0
  137. package/dist/utils/omit.js +28 -0
  138. package/dist/utils/omit.js.map +1 -0
  139. package/dist/utils/pagination.d.ts +48 -0
  140. package/dist/utils/pagination.d.ts.map +1 -0
  141. package/dist/utils/pagination.js +42 -0
  142. package/dist/utils/pagination.js.map +1 -0
  143. package/dist/utils/pick.d.ts +20 -0
  144. package/dist/utils/pick.d.ts.map +1 -0
  145. package/dist/utils/pick.js +28 -0
  146. package/dist/utils/pick.js.map +1 -0
  147. package/dist/utils/serialize-dates.d.ts +27 -0
  148. package/dist/utils/serialize-dates.d.ts.map +1 -0
  149. package/dist/utils/serialize-dates.js +39 -0
  150. package/dist/utils/serialize-dates.js.map +1 -0
  151. package/dist/utils.d.ts +9 -0
  152. package/dist/utils.d.ts.map +1 -0
  153. package/dist/utils.js +31 -0
  154. package/dist/utils.js.map +1 -0
  155. package/package.json +79 -0
@@ -0,0 +1,249 @@
1
+ import { describeRoute as honoDescribeRoute } from 'hono-openapi';
2
+ import { resolver, validator } from 'hono-openapi/valibot';
3
+ import { getClientIp } from './get-client-ip.js';
4
+ /**
5
+ * Global storage for OpenAPI response hooks.
6
+ * Set during bootstrap and used when defining routes.
7
+ */
8
+ let globalResponseHooks = [];
9
+ /**
10
+ * Set the global OpenAPI response hooks.
11
+ * This is called during bootstrap and makes hooks available to route definitions.
12
+ *
13
+ * @internal
14
+ */
15
+ export function setGlobalResponseHooks(hooks) {
16
+ globalResponseHooks = hooks;
17
+ }
18
+ /**
19
+ * Get the current global OpenAPI response hooks.
20
+ *
21
+ * @internal
22
+ */
23
+ export function getGlobalResponseHooks() {
24
+ return globalResponseHooks;
25
+ }
26
+ /**
27
+ * HTTP status codes with descriptions
28
+ */
29
+ const STATUS_DESCRIPTIONS = {
30
+ 200: 'Success',
31
+ 201: 'Created',
32
+ 204: 'No Content',
33
+ 400: 'Bad Request',
34
+ 401: 'Unauthorized',
35
+ 403: 'Forbidden',
36
+ 404: 'Not Found',
37
+ 409: 'Conflict',
38
+ 422: 'Unprocessable Entity',
39
+ 429: 'Too Many Requests',
40
+ 500: 'Internal Server Error',
41
+ };
42
+ /**
43
+ * Create routes with typed service injection.
44
+ *
45
+ * @template TServices - The shape of services required by these routes
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * interface AuthRoutesServices {
50
+ * authService: AuthService;
51
+ * userService: UserService;
52
+ * }
53
+ *
54
+ * export const authRoutes = createRoutes<AuthRoutesServices>((router, services) => {
55
+ * // services.authService is typed as AuthService!
56
+ * // services.userService is typed as UserService!
57
+ * router.post('/login', ...);
58
+ * });
59
+ * ```
60
+ */
61
+ export function createRoutes(factory) {
62
+ return factory;
63
+ }
64
+ /**
65
+ * Create a route handler with validation and OpenAPI metadata.
66
+ *
67
+ * Type inference works automatically:
68
+ * - Body type is inferred from the `body` schema
69
+ * - Return type is inferred from the `responses[200]` schema
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * route({
74
+ * body: StartSessionDto,
75
+ * responses: { 200: StartSessionResponseDto },
76
+ * handler: async ({ body }) => {
77
+ * // body is typed as InferOutput<typeof StartSessionDto>
78
+ * return { email: body.email }; // Must match StartSessionResponseDto
79
+ * }
80
+ * })
81
+ * ```
82
+ */
83
+ export function route(config) {
84
+ const middlewares = [];
85
+ // Add OpenAPI metadata
86
+ middlewares.push(buildOpenAPIMiddleware(config));
87
+ // Add custom middleware (e.g., auth) before validation
88
+ if (config.middleware) {
89
+ middlewares.push(...config.middleware);
90
+ }
91
+ // Add validation middleware with custom error handler (returns 422 instead of 400)
92
+ if (config.body) {
93
+ middlewares.push(validator('json', config.body, (result, c) => {
94
+ if (!result.success) {
95
+ return c.json({ error: 'Validation failed', issues: result.issues }, 422);
96
+ }
97
+ }));
98
+ }
99
+ if (config.query) {
100
+ middlewares.push(validator('query', config.query, (result, c) => {
101
+ if (!result.success) {
102
+ return c.json({ error: 'Validation failed', issues: result.issues }, 422);
103
+ }
104
+ }));
105
+ }
106
+ if (config.params) {
107
+ middlewares.push(validator('param', config.params, (result, c) => {
108
+ if (!result.success) {
109
+ return c.json({ error: 'Validation failed', issues: result.issues }, 422);
110
+ }
111
+ }));
112
+ }
113
+ // Add handler wrapper
114
+ middlewares.push(async (c) => {
115
+ const routeContext = buildRouteContext(c);
116
+ const result = await config.handler(
117
+ // @ts-expect-error - Session typing issue to be resolved separately
118
+ routeContext);
119
+ // If handler returned a Response, return it directly (for custom responses like text)
120
+ if (result instanceof Response) {
121
+ return result;
122
+ }
123
+ // Return appropriate response
124
+ if (result === null || result === undefined) {
125
+ c.status(204);
126
+ return c.body(null);
127
+ }
128
+ return c.json(result);
129
+ });
130
+ return middlewares;
131
+ }
132
+ function buildOpenAPIMiddleware(config) {
133
+ const responses = buildOpenAPIResponses(config);
134
+ const openApiConfig = {
135
+ operationId: config.operationId || config.summary,
136
+ tags: config.tags,
137
+ summary: config.summary,
138
+ description: config.description,
139
+ responses: responses,
140
+ security: config.public ? [] : [{ bearerAuth: [] }],
141
+ ...(config.paginate && { parameters: buildPaginationParameters() }),
142
+ ...config.openapi,
143
+ };
144
+ return honoDescribeRoute(openApiConfig);
145
+ }
146
+ /**
147
+ * Build pagination parameters for OpenAPI spec
148
+ */
149
+ function buildPaginationParameters() {
150
+ return [
151
+ {
152
+ name: 'page',
153
+ in: 'query',
154
+ required: false,
155
+ schema: {
156
+ type: 'integer',
157
+ format: 'int32',
158
+ minimum: 1,
159
+ maximum: 2147483647, // Max int32
160
+ default: 1,
161
+ },
162
+ description: 'Page number',
163
+ },
164
+ {
165
+ name: 'limit',
166
+ in: 'query',
167
+ required: false,
168
+ schema: {
169
+ type: 'integer',
170
+ format: 'int32',
171
+ minimum: 1,
172
+ maximum: 100,
173
+ default: 50,
174
+ },
175
+ description: 'Number of items per page',
176
+ },
177
+ ];
178
+ }
179
+ function buildOpenAPIResponses(config) {
180
+ const responses = {};
181
+ // Add default error responses
182
+ const defaultResponses = config.public
183
+ ? { 400: undefined, 429: undefined, 500: undefined }
184
+ : { 400: undefined, 401: undefined, 429: undefined, 500: undefined };
185
+ const allResponses = { ...defaultResponses, ...(config.responses || {}) };
186
+ // Get global hooks
187
+ const hooks = getGlobalResponseHooks();
188
+ for (const [statusCode, schema] of Object.entries(allResponses)) {
189
+ const code = Number(statusCode);
190
+ const description = STATUS_DESCRIPTIONS[code] || 'Response';
191
+ // Determine if this is an error response that should use default error schema
192
+ const isErrorResponse = code >= 400;
193
+ const shouldUseDefaultErrorSchema = isErrorResponse && !schema;
194
+ // Start with base response (no headers)
195
+ let response = {
196
+ description,
197
+ ...(schema
198
+ ? {
199
+ content: {
200
+ 'application/json': {
201
+ // resolver type only supports BaseSchema, but works with BaseSchemaAsync at runtime
202
+ // @ts-expect-error - resolver accepts both BaseSchema and BaseSchemaAsync
203
+ schema: resolver(schema, { errorMode: 'ignore' }),
204
+ },
205
+ },
206
+ }
207
+ : shouldUseDefaultErrorSchema
208
+ ? {
209
+ content: {
210
+ 'application/json': {
211
+ schema: {
212
+ $ref: '#/components/schemas/ErrorResponse',
213
+ },
214
+ },
215
+ },
216
+ }
217
+ : {}),
218
+ };
219
+ // Apply hooks to build up headers
220
+ for (const hook of hooks) {
221
+ response = hook(response, {
222
+ statusCode,
223
+ routeConfig: {
224
+ public: config.public,
225
+ paginate: config.paginate,
226
+ tags: config.tags,
227
+ summary: config.summary,
228
+ },
229
+ });
230
+ }
231
+ responses[statusCode] = response;
232
+ }
233
+ return responses;
234
+ }
235
+ function buildRouteContext(c) {
236
+ const req = c.req;
237
+ return {
238
+ body: req.valid('json'),
239
+ query: req.valid('query'),
240
+ params: req.valid('param'),
241
+ services: c.get('services') || {},
242
+ // @ts-expect-error - Session typing issue to be resolved separately
243
+ session: c.get('session'),
244
+ ip: getClientIp(c),
245
+ userAgent: c.req.header('user-agent'),
246
+ context: c,
247
+ };
248
+ }
249
+ //# sourceMappingURL=route-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-helpers.js","sourceRoot":"","sources":["../src/route-helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Type helper to recursively convert Date fields to string in an object type
3
+ */
4
+ export type SerializedDates<T> = T extends Date ? string : T extends Array<infer U> ? Array<SerializedDates<U>> : T extends object ? {
5
+ [K in keyof T]: SerializedDates<T[K]>;
6
+ } : T;
7
+ /**
8
+ * Recursively converts all Date objects to ISO 8601 strings in an entity or array of entities.
9
+ *
10
+ * This utility handles the mismatch between Prisma's Date types and API response DTOs
11
+ * that expect string timestamps. It preserves null values and handles nested objects and arrays.
12
+ *
13
+ * @template T - The type of data to transform
14
+ * @param data - The data to transform (object, array, or primitive)
15
+ * @returns The same structure with all Date objects converted to ISO strings
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const ranger = await prisma.ranger.findUnique({
20
+ * where: { id },
21
+ * include: { category: true }
22
+ * });
23
+ * return serializeDates(ranger); // All Date fields → ISO strings
24
+ * ```
25
+ */
26
+ export declare function serializeDates<T>(data: T): SerializedDates<T>;
27
+ //# sourceMappingURL=serialize-dates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize-dates.d.ts","sourceRoot":"","sources":["../src/serialize-dates.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GAC3C,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;AAEV;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAsB7D"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Recursively converts all Date objects to ISO 8601 strings in an entity or array of entities.
3
+ *
4
+ * This utility handles the mismatch between Prisma's Date types and API response DTOs
5
+ * that expect string timestamps. It preserves null values and handles nested objects and arrays.
6
+ *
7
+ * @template T - The type of data to transform
8
+ * @param data - The data to transform (object, array, or primitive)
9
+ * @returns The same structure with all Date objects converted to ISO strings
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const ranger = await prisma.ranger.findUnique({
14
+ * where: { id },
15
+ * include: { category: true }
16
+ * });
17
+ * return serializeDates(ranger); // All Date fields → ISO strings
18
+ * ```
19
+ */
20
+ export function serializeDates(data) {
21
+ if (data === null || data === undefined) {
22
+ return data;
23
+ }
24
+ if (data instanceof Date) {
25
+ return data.toISOString();
26
+ }
27
+ if (Array.isArray(data)) {
28
+ return data.map((item) => serializeDates(item));
29
+ }
30
+ if (typeof data === 'object') {
31
+ const result = {};
32
+ for (const [key, value] of Object.entries(data)) {
33
+ result[key] = serializeDates(value);
34
+ }
35
+ return result;
36
+ }
37
+ return data;
38
+ }
39
+ //# sourceMappingURL=serialize-dates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize-dates.js","sourceRoot":"","sources":["../src/serialize-dates.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,cAAc,CAAI,IAAO;IACvC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,IAA0B,CAAC;IACpC,CAAC;IAED,IAAI,IAAI,YAAY,IAAI,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,WAAW,EAAwB,CAAC;IAClD,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAuB,CAAC;IACxE,CAAC;IAED,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,MAA4B,CAAC;IACtC,CAAC;IAED,OAAO,IAA0B,CAAC;AACpC,CAAC"}
@@ -0,0 +1,87 @@
1
+ import type { AwilixContainer } from 'awilix';
2
+ import { Hono } from 'hono';
3
+ import type { ModuleConfig } from './types.js';
4
+ /**
5
+ * Options for creating a test app
6
+ */
7
+ export interface CreateTestAppOptions {
8
+ /**
9
+ * API base path
10
+ * @default '/api'
11
+ */
12
+ apiBasePath?: string;
13
+ /**
14
+ * Service mocks to override module providers
15
+ * These will be registered as values in the container, overriding any class/factory providers
16
+ */
17
+ services?: Record<string, unknown>;
18
+ /**
19
+ * Error handler
20
+ * @default defaultErrorHandler
21
+ * Set to false to disable error handling
22
+ */
23
+ errorHandler?: false | import('hono').ErrorHandler;
24
+ /**
25
+ * Enable debug logging
26
+ * @default false
27
+ */
28
+ debug?: boolean;
29
+ }
30
+ /**
31
+ * Test context returned by createTestApp
32
+ */
33
+ export interface TestContext {
34
+ /**
35
+ * Hono app instance with mounted routes
36
+ */
37
+ app: Hono;
38
+ /**
39
+ * Awilix container with all services
40
+ */
41
+ container: AwilixContainer;
42
+ }
43
+ /**
44
+ * Create a test app for testing a specific module.
45
+ *
46
+ * This function:
47
+ * 1. Creates an Awilix container
48
+ * 2. Registers all module providers (from the module and its imports)
49
+ * 3. Overrides providers with mock services
50
+ * 4. Creates a Hono app with error handler
51
+ * 5. Mounts the module's routes
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const mockEmailService = {
56
+ * sendEmail: vi.fn(),
57
+ * };
58
+ *
59
+ * const { app, container } = createTestApp(AuthModule, {
60
+ * services: {
61
+ * emailService: mockEmailService,
62
+ * prismaService: testDb,
63
+ * },
64
+ * });
65
+ *
66
+ * // Test routes
67
+ * const res = await app.request('/api/auth/session', { method: 'POST', ... });
68
+ * ```
69
+ */
70
+ export declare function createTestApp(module: ModuleConfig, options?: CreateTestAppOptions): TestContext;
71
+ /**
72
+ * Create a test container with specific services.
73
+ *
74
+ * Useful for testing services in isolation without creating a full app.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const container = createTestContainer({
79
+ * prismaService: testDb,
80
+ * hashService: new HashService(),
81
+ * });
82
+ *
83
+ * const authService = new AuthService(container.cradle);
84
+ * ```
85
+ */
86
+ export declare function createTestContainer(services: Record<string, unknown>): AwilixContainer;
87
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,MAAM,EAAE,YAAY,CAAC;IAEnD;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,GAAG,EAAE,IAAI,CAAC;IAEV;;OAEG;IACH,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,oBAAyB,GACjC,WAAW,CA+Cb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAatF"}
@@ -0,0 +1,94 @@
1
+ import { asValue, createContainer, InjectionMode } from 'awilix';
2
+ import { Hono } from 'hono';
3
+ import { collectModules, registerModuleProviders, validateNoCycles } from './bootstrap.js';
4
+ import { defaultErrorHandler } from './error-handler.js';
5
+ /**
6
+ * Create a test app for testing a specific module.
7
+ *
8
+ * This function:
9
+ * 1. Creates an Awilix container
10
+ * 2. Registers all module providers (from the module and its imports)
11
+ * 3. Overrides providers with mock services
12
+ * 4. Creates a Hono app with error handler
13
+ * 5. Mounts the module's routes
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const mockEmailService = {
18
+ * sendEmail: vi.fn(),
19
+ * };
20
+ *
21
+ * const { app, container } = createTestApp(AuthModule, {
22
+ * services: {
23
+ * emailService: mockEmailService,
24
+ * prismaService: testDb,
25
+ * },
26
+ * });
27
+ *
28
+ * // Test routes
29
+ * const res = await app.request('/api/auth/session', { method: 'POST', ... });
30
+ * ```
31
+ */
32
+ export function createTestApp(module, options = {}) {
33
+ const { apiBasePath = '/api', services = {}, errorHandler = defaultErrorHandler, debug = false, } = options;
34
+ // Create Awilix container
35
+ const container = createContainer({
36
+ injectionMode: InjectionMode.PROXY,
37
+ strict: true,
38
+ });
39
+ // Collect all modules (flatten imports)
40
+ const allModules = collectModules(module);
41
+ validateNoCycles(allModules);
42
+ // Register all providers from the module and its imports
43
+ for (const mod of allModules) {
44
+ registerModuleProviders(mod, container, debug);
45
+ }
46
+ // Override with mock services AFTER module providers
47
+ // This allows both overriding services and providing dependencies
48
+ for (const [key, value] of Object.entries(services)) {
49
+ container.register({
50
+ [key]: asValue(value),
51
+ });
52
+ }
53
+ // Create Hono app
54
+ const app = new Hono();
55
+ // Apply error handler
56
+ if (errorHandler !== false) {
57
+ app.onError(errorHandler);
58
+ }
59
+ // Mount module routes
60
+ if (module.routes && module.basePath) {
61
+ const router = new Hono();
62
+ module.routes(router, container.cradle);
63
+ app.route(`${apiBasePath}/${module.basePath}`, router);
64
+ }
65
+ return { app, container };
66
+ }
67
+ /**
68
+ * Create a test container with specific services.
69
+ *
70
+ * Useful for testing services in isolation without creating a full app.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const container = createTestContainer({
75
+ * prismaService: testDb,
76
+ * hashService: new HashService(),
77
+ * });
78
+ *
79
+ * const authService = new AuthService(container.cradle);
80
+ * ```
81
+ */
82
+ export function createTestContainer(services) {
83
+ const container = createContainer({
84
+ injectionMode: InjectionMode.PROXY,
85
+ strict: true,
86
+ });
87
+ for (const [key, value] of Object.entries(services)) {
88
+ container.register({
89
+ [key]: asValue(value),
90
+ });
91
+ }
92
+ return container;
93
+ }
94
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAgDzD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAoB,EACpB,UAAgC,EAAE;IAElC,MAAM,EACJ,WAAW,GAAG,MAAM,EACpB,QAAQ,GAAG,EAAE,EACb,YAAY,GAAG,mBAAmB,EAClC,KAAK,GAAG,KAAK,GACd,GAAG,OAAO,CAAC;IAEZ,0BAA0B;IAC1B,MAAM,SAAS,GAAG,eAAe,CAAC;QAChC,aAAa,EAAE,aAAa,CAAC,KAAK;QAClC,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IAEH,wCAAwC;IACxC,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE7B,yDAAyD;IACzD,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,uBAAuB,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;IAED,qDAAqD;IACrD,kEAAkE;IAClE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,SAAS,CAAC,QAAQ,CAAC;YACjB,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC;SACtB,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB;IAClB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,sBAAsB;IACtB,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3B,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IAED,sBAAsB;IACtB,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,MAAiC,CAAC,CAAC;QACnE,GAAG,CAAC,KAAK,CAAC,GAAG,WAAW,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAiC;IACnE,MAAM,SAAS,GAAG,eAAe,CAAC;QAChC,aAAa,EAAE,aAAa,CAAC,KAAK;QAClC,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IAEH,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,SAAS,CAAC,QAAQ,CAAC;YACjB,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC;SACtB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}