@twasik4/pocket-service 1.0.3 → 1.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.
@@ -1,442 +0,0 @@
1
- import {
2
- NextFunction,
3
- Request,
4
- RequestHandler,
5
- Response,
6
- Router,
7
- } from "express";
8
- import z, { ZodSchema, output } from "zod";
9
-
10
- export type RouteMeta = {
11
- description?: string;
12
- deprecated?: boolean;
13
- tags?: string[];
14
- authRequired?: boolean;
15
- rateLimit?: "standard" | "strict" | "none";
16
- version?: string;
17
- [key: string]: any;
18
- };
19
-
20
- export type AuthenticatedUser = {
21
- userId: string;
22
- [key: string]: string | string[] | number | boolean;
23
- };
24
-
25
- export type AuthResolver = (
26
- req: Request,
27
- route: RouteDefinition<any, any, boolean>,
28
- ) => AuthenticatedUser | null | Promise<AuthenticatedUser | null>;
29
-
30
- export type AuthStrategy = {
31
- name: string;
32
- canHandle?: (
33
- req: Request,
34
- route: RouteDefinition<any, any, boolean>,
35
- ) => boolean | Promise<boolean>;
36
- authenticate: AuthResolver;
37
- };
38
-
39
- export function defaultHeaderAuthResolver(
40
- req: Request,
41
- ): AuthenticatedUser | null {
42
- const userIdHeader = req.headers["x-user-id"];
43
- const userMetaHeader = req.headers["x-user-meta"];
44
-
45
- if (typeof userMetaHeader === "string") {
46
- try {
47
- const parsed = JSON.parse(userMetaHeader) as Partial<AuthenticatedUser>;
48
-
49
- if (typeof parsed.userId === "string") {
50
- return parsed as AuthenticatedUser;
51
- }
52
- } catch {
53
- return null;
54
- }
55
- }
56
-
57
- if (typeof userIdHeader !== "string") {
58
- return null;
59
- }
60
-
61
- return {
62
- userId: userIdHeader,
63
- };
64
- }
65
-
66
- export function createAuthResolver(
67
- strategies: AuthStrategy[],
68
- opts: { fallbackResolver?: AuthResolver } = {},
69
- ): AuthResolver {
70
- return async (req, route) => {
71
- for (const strategy of strategies) {
72
- if (strategy.canHandle) {
73
- const canHandle = await strategy.canHandle(req, route);
74
- if (!canHandle) {
75
- continue;
76
- }
77
- }
78
-
79
- const user = await strategy.authenticate(req, route);
80
- if (user) {
81
- return user;
82
- }
83
- }
84
-
85
- if (opts.fallbackResolver) {
86
- return opts.fallbackResolver(req, route);
87
- }
88
-
89
- return null;
90
- };
91
- }
92
-
93
- export type TypedRequest<
94
- TBody = unknown,
95
- TParams = unknown,
96
- TRequireAuth extends boolean = false,
97
- > = Request<
98
- TParams extends ZodSchema ? output<TParams> : Record<string, string>,
99
- any,
100
- TBody extends ZodSchema ? output<TBody> : any
101
- > &
102
- (TRequireAuth extends true
103
- ? { user: AuthenticatedUser }
104
- : { user?: AuthenticatedUser });
105
-
106
- export type TypedRequestHandler<
107
- TBody = unknown,
108
- TParams = unknown,
109
- TRequireAuth extends boolean = false,
110
- > = (
111
- req: TypedRequest<TBody, TParams, TRequireAuth>,
112
- res: Response,
113
- next: NextFunction,
114
- ) => unknown;
115
-
116
- /**
117
- * Defines a route with enhanced type safety and built-in support for request validation and authentication. This type is used in conjunction with the `addRoute` function to register routes on an Express router, allowing you to specify the HTTP method, path, authentication requirements, and Zod schemas for validating request bodies and URL parameters. The route definition also includes optional metadata that can be used for documentation or analytics purposes.
118
- */
119
- export type RouteDefinition<
120
- TBody extends ZodSchema | undefined = undefined,
121
- TParams extends ZodSchema | undefined = undefined,
122
- TRequireAuth extends boolean = false,
123
- > = {
124
- method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
125
- fullPath: string;
126
- requireAuth?: TRequireAuth;
127
- /**
128
- * Optional Zod schema to validate the request body. Only applicable for non-GET requests. If provided, the middleware will automatically validate the request body against this schema and return a 400 error if validation fails.
129
- */
130
- bodyValidator?: TBody;
131
- /**
132
- * Optional Zod schema to validate URL parameters (e.g., :id in /users/:id). If provided, the middleware will automatically validate the URL parameters against this schema and return a 400 error if validation fails.
133
- */
134
- paramsValidator?: TParams;
135
- /**
136
- * Optional metadata for the route, which can be used for documentation, analytics, or other purposes.
137
- */
138
- meta?: RouteMeta;
139
- };
140
-
141
- const registeredRoutes: RouteDefinition<any, any, boolean>[] = [];
142
-
143
- export function getRegisteredRoutes() {
144
- return registeredRoutes;
145
- }
146
-
147
- type CreateMetaRouterOptions = {
148
- authResolver?: AuthResolver;
149
- onUnauthorized?: (req: Request, res: Response) => void;
150
- onAuthError?: (err: unknown, req: Request, res: Response) => void;
151
- };
152
-
153
- /**
154
- * Utility function to create an Express router with enhanced type safety and built-in support for request validation and authentication. This function allows you to define routes with associated Zod schemas for validating request bodies and URL parameters, as well as specifying whether authentication is required for each route. The returned object includes the configured Express router, a list of registered routes for introspection, and a helper function to add new routes with the specified configurations.
155
- */
156
- export function createMetaRouter(): {
157
- /**
158
- * The Express router instance that can be used to register routes and middleware. This router is configured to work with the enhanced route definitions provided by the `addRoute` function, which supports request validation and authentication requirements.
159
- */
160
- router: Router;
161
- /**
162
- * List of registered routes with their configurations, which can be used for introspection, documentation generation, or analytics purposes. Each entry includes the HTTP method, full path, authentication requirement, validation schemas, and any additional metadata provided during route registration.
163
- */
164
- routes: RouteDefinition<any, any, boolean>[];
165
- /**
166
- * Adds a new route to the router with the specified configuration and request handler.
167
- *
168
- * - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.
169
- * - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.
170
- *
171
- * The route definition is also stored in an internal list for introspection purposes.
172
- *
173
- * @param route The route definition, including method, path, validation schemas, and authentication requirements.
174
- * @param handler The request handler function for the route.
175
- * @returns void
176
- *
177
- * @example
178
- * addRoute(
179
- * {
180
- * fullPath: "/me",
181
- * method: "GET",
182
- * requireAuth: true,
183
- * meta: {
184
- * description: "Get current authenticated user info",
185
- * },
186
- * },
187
- * async (req, res) => {
188
- * try {
189
- * ...fetch user info from database using req.user.userId...
190
- * res.status(200).json(...user info...);
191
- * } catch (err) {
192
- * console.error("Failed to fetch user info:", err);
193
- * res
194
- * .status(500)
195
- * .json({ isSuccess: false, message: "Internal server error" });
196
- * }
197
- * },
198
- * );
199
- */
200
- addRoute: <
201
- TBody extends ZodSchema | undefined = undefined,
202
- TParams extends ZodSchema | undefined = undefined,
203
- TRequireAuth extends boolean = false,
204
- >(
205
- route: RouteDefinition<TBody, TParams, TRequireAuth>,
206
- /** Request handler for the route */ handler: TypedRequestHandler<
207
- TBody,
208
- TParams,
209
- TRequireAuth
210
- >,
211
- ) => void;
212
- } {
213
- return createMetaRouterWithAuth();
214
- }
215
-
216
- export function createMetaRouterWithAuth(options?: CreateMetaRouterOptions): {
217
- /**
218
- * The Express router instance that can be used to register routes and middleware. This router is configured to work with the enhanced route definitions provided by the `addRoute` function, which supports request validation and authentication requirements.
219
- */
220
- router: Router;
221
- /**
222
- * List of registered routes with their configurations, which can be used for introspection, documentation generation, or analytics purposes. Each entry includes the HTTP method, full path, authentication requirement, validation schemas, and any additional metadata provided during route registration.
223
- */
224
- routes: RouteDefinition<any, any, boolean>[];
225
- /**
226
- * Adds a new route to the router with the specified configuration and request handler.
227
- *
228
- * - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.
229
- * - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.
230
- *
231
- * The route definition is also stored in an internal list for introspection purposes.
232
- *
233
- * @param route The route definition, including method, path, validation schemas, and authentication requirements.
234
- * @param handler The request handler function for the route.
235
- * @returns void
236
- *
237
- * @example
238
- * addRoute(
239
- * {
240
- * fullPath: "/me",
241
- * method: "GET",
242
- * requireAuth: true,
243
- * meta: {
244
- * description: "Get current authenticated user info",
245
- * },
246
- * },
247
- * async (req, res) => {
248
- * try {
249
- * ...fetch user info from database using req.user.userId...
250
- * res.status(200).json(...user info...);
251
- * } catch (err) {
252
- * console.error("Failed to fetch user info:", err);
253
- * res
254
- * .status(500)
255
- * .json({ isSuccess: false, message: "Internal server error" });
256
- * }
257
- * },
258
- * );
259
- */
260
- addRoute: <
261
- TBody extends ZodSchema | undefined = undefined,
262
- TParams extends ZodSchema | undefined = undefined,
263
- TRequireAuth extends boolean = false,
264
- >(
265
- route: RouteDefinition<TBody, TParams, TRequireAuth>,
266
- /** Request handler for the route */ handler: TypedRequestHandler<
267
- TBody,
268
- TParams,
269
- TRequireAuth
270
- >,
271
- ) => void;
272
- } {
273
- const router = Router();
274
- const routes: RouteDefinition<any, any, boolean>[] = [];
275
- const authResolver: AuthResolver =
276
- options?.authResolver ?? ((req) => defaultHeaderAuthResolver(req));
277
- const onUnauthorized =
278
- options?.onUnauthorized ??
279
- ((_: Request, res: Response) => {
280
- res.status(401).json({
281
- isSuccess: false,
282
- message: "Unauthorized",
283
- });
284
- });
285
- const onAuthError =
286
- options?.onAuthError ??
287
- ((_: unknown, __: Request, res: Response) => {
288
- res.status(401).json({
289
- isSuccess: false,
290
- message: "Unauthorized",
291
- });
292
- });
293
-
294
- /**
295
- * Adds a new route to the router with the specified configuration and request handler.
296
- *
297
- * - If `requireAuth` is set to true, the route will automatically include middleware to check for an authenticated user and return a 401 error if the user is not authenticated.
298
- * - If `bodyValidator` or `paramsValidator` are provided, the route will include middleware to validate the request body and URL parameters against the provided Zod schemas, returning a 400 error if validation fails.
299
- *
300
- * The route definition is also stored in an internal list for introspection purposes.
301
- * @param route The route definition, including method, path, validation schemas, and authentication requirements.
302
- * @param handler The request handler function for the route.
303
- *
304
- * @example
305
- * addRoute(
306
- * {
307
- * fullPath: "/me",
308
- * method: "GET",
309
- * requireAuth: true,
310
- * meta: {
311
- * description: "Get current authenticated user info",
312
- * },
313
- * },
314
- * async (req, res) => {
315
- * try {
316
- * ...fetch user info from database using req.user.userId...
317
- * res.status(200).json(...user info...);
318
- * } catch (err) {
319
- * console.error("Failed to fetch user info:", err);
320
- * res
321
- * .status(500)
322
- * .json({ isSuccess: false, message: "Internal server error" });
323
- * }
324
- * },
325
- * );
326
- */
327
- function addRoute<
328
- TBody extends ZodSchema | undefined = undefined,
329
- TParams extends ZodSchema | undefined = undefined,
330
- TRequireAuth extends boolean = false,
331
- >(
332
- route: RouteDefinition<TBody, TParams, TRequireAuth>,
333
- /** Request handler for the route */ handler: TypedRequestHandler<
334
- TBody,
335
- TParams,
336
- TRequireAuth
337
- >,
338
- ) {
339
- const middlewareStack: RequestHandler[] = [];
340
- const bodyValidator = route.bodyValidator;
341
- const paramsValidator = route.paramsValidator;
342
-
343
- routes.push({
344
- method: route.method,
345
- fullPath: route.fullPath,
346
- requireAuth: route.requireAuth ?? false,
347
- meta: route.meta,
348
- bodyValidator,
349
- paramsValidator,
350
- });
351
- if (route.requireAuth) {
352
- middlewareStack.push(async (req, res, next) => {
353
- try {
354
- const user = await authResolver(req, route);
355
-
356
- if (!user) {
357
- onUnauthorized(req, res);
358
- return;
359
- }
360
-
361
- (req as TypedRequest<TBody, TParams, true>).user = user;
362
- next();
363
- } catch (err) {
364
- onAuthError(err, req, res);
365
- }
366
- });
367
- }
368
- if (bodyValidator && route.method !== "GET") {
369
- middlewareStack.push((req, res, next) => {
370
- let value = req.body;
371
- if (typeof value === "string") {
372
- if (
373
- bodyValidator instanceof z.ZodObject ||
374
- bodyValidator instanceof z.ZodArray ||
375
- bodyValidator.def.type === "object" ||
376
- bodyValidator.def.type === "array"
377
- ) {
378
- try {
379
- value = JSON.parse(value);
380
- } catch (e) {
381
- return res.status(400).json({
382
- isSuccess: false,
383
- message: "Invalid JSON in request body",
384
- error: e instanceof Error ? e.message : e,
385
- });
386
- }
387
- } else if (
388
- bodyValidator instanceof z.ZodNumber ||
389
- bodyValidator.def.type === "number"
390
- ) {
391
- try {
392
- value = Number(value);
393
- } catch (e) {
394
- return res.status(400).json({
395
- isSuccess: false,
396
- message: "Invalid number in request body",
397
- error: e instanceof Error ? e.message : e,
398
- });
399
- }
400
- }
401
- }
402
- const validationResult = bodyValidator.safeParse(value);
403
- if (validationResult.success) {
404
- req.body = validationResult.data;
405
- next();
406
- } else {
407
- res.status(400).json({
408
- isSuccess: false,
409
- message: "Invalid request body",
410
- error: validationResult.error,
411
- });
412
- }
413
- });
414
- }
415
- if (paramsValidator) {
416
- middlewareStack.push((req, res, next) => {
417
- const validationResult = paramsValidator.safeParse(req.params);
418
- if (validationResult.success) {
419
- req.params = validationResult.data as Request["params"];
420
- next();
421
- } else {
422
- res.status(400).json({
423
- isSuccess: false,
424
- message: "Invalid URL parameters",
425
- error: validationResult.error,
426
- });
427
- }
428
- });
429
- }
430
- (router as any)[route.method.toLowerCase()](
431
- route.fullPath,
432
- ...middlewareStack,
433
- handler as RequestHandler,
434
- );
435
- }
436
-
437
- return {
438
- router,
439
- routes,
440
- addRoute,
441
- };
442
- }
@@ -1,7 +0,0 @@
1
- export * from "./service";
2
- export * from "./stream-handler";
3
- export * from "./types";
4
- export * from "./express-types";
5
- export * from "./auth/strategies";
6
- export * from "./db/mongo/mongo";
7
- export * from "./db/mongo/collection";
@@ -1,19 +0,0 @@
1
- import winston, { format, transports } from "winston";
2
-
3
- export const createLogger = <T = string>(service: T) =>
4
- winston.createLogger({
5
- format: format.json({ bigint: true }),
6
- defaultMeta: { service },
7
- transports: [
8
- new transports.Console({
9
- format: format.combine(
10
- format.colorize(),
11
- format.timestamp(),
12
- format.printf(
13
- ({ level, message, timestamp }) =>
14
- `${timestamp} [${service}] ${level}: ${message}`,
15
- ),
16
- ),
17
- }),
18
- ],
19
- });