express-model-binding 1.0.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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +208 -0
  3. package/dist/BaseAdapter-BjvLQijd.d.mts +214 -0
  4. package/dist/BaseAdapter-BjvLQijd.d.ts +214 -0
  5. package/dist/adapters/KnexAdapter.d.mts +44 -0
  6. package/dist/adapters/KnexAdapter.d.ts +44 -0
  7. package/dist/adapters/KnexAdapter.js +257 -0
  8. package/dist/adapters/KnexAdapter.js.map +1 -0
  9. package/dist/adapters/KnexAdapter.mjs +229 -0
  10. package/dist/adapters/KnexAdapter.mjs.map +1 -0
  11. package/dist/adapters/MongooseAdapter.d.mts +30 -0
  12. package/dist/adapters/MongooseAdapter.d.ts +30 -0
  13. package/dist/adapters/MongooseAdapter.js +245 -0
  14. package/dist/adapters/MongooseAdapter.js.map +1 -0
  15. package/dist/adapters/MongooseAdapter.mjs +225 -0
  16. package/dist/adapters/MongooseAdapter.mjs.map +1 -0
  17. package/dist/adapters/PrismaAdapter.d.mts +42 -0
  18. package/dist/adapters/PrismaAdapter.d.ts +42 -0
  19. package/dist/adapters/PrismaAdapter.js +247 -0
  20. package/dist/adapters/PrismaAdapter.js.map +1 -0
  21. package/dist/adapters/PrismaAdapter.mjs +220 -0
  22. package/dist/adapters/PrismaAdapter.mjs.map +1 -0
  23. package/dist/adapters/SequelizeAdapter.d.mts +27 -0
  24. package/dist/adapters/SequelizeAdapter.d.ts +27 -0
  25. package/dist/adapters/SequelizeAdapter.js +280 -0
  26. package/dist/adapters/SequelizeAdapter.js.map +1 -0
  27. package/dist/adapters/SequelizeAdapter.mjs +260 -0
  28. package/dist/adapters/SequelizeAdapter.mjs.map +1 -0
  29. package/dist/adapters/TypeORMAdapter.d.mts +26 -0
  30. package/dist/adapters/TypeORMAdapter.d.ts +26 -0
  31. package/dist/adapters/TypeORMAdapter.js +294 -0
  32. package/dist/adapters/TypeORMAdapter.js.map +1 -0
  33. package/dist/adapters/TypeORMAdapter.mjs +267 -0
  34. package/dist/adapters/TypeORMAdapter.mjs.map +1 -0
  35. package/dist/index.d.mts +411 -0
  36. package/dist/index.d.ts +411 -0
  37. package/dist/index.js +1514 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/index.mjs +1450 -0
  40. package/dist/index.mjs.map +1 -0
  41. package/package.json +148 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors/index.ts","../../src/core/BaseAdapter.ts","../../src/utils/validators.ts","../../src/adapters/TypeORMAdapter.ts"],"sourcesContent":["/**\n * Base error class for model binding errors\n */\nexport class BindingError extends Error {\n public readonly originalError?: Error;\n\n constructor(message: string, originalError?: Error) {\n super(message);\n this.name = 'BindingError';\n this.originalError = originalError;\n Error.captureStackTrace(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n originalError: this.originalError?.message,\n };\n }\n}\n\n/**\n * Error thrown when a model is not found (404)\n */\nexport class ModelNotFoundError extends Error {\n public readonly statusCode = 404;\n public readonly paramName: string;\n public readonly paramValue: string;\n public readonly modelName: string;\n\n constructor(paramName: string, paramValue: string, modelName: string, customMessage?: string) {\n super(customMessage || `${modelName} not found with ${paramName} = ${paramValue}`);\n this.name = 'ModelNotFoundError';\n this.paramName = paramName;\n this.paramValue = paramValue;\n this.modelName = modelName;\n Error.captureStackTrace(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n error: 'Not Found',\n message: this.message,\n statusCode: this.statusCode,\n param: this.paramName,\n value: this.paramValue,\n model: this.modelName,\n };\n }\n}\n\n/**\n * Error thrown when no adapter is configured\n */\nexport class AdapterNotSetError extends Error {\n constructor(\n message: string = 'No adapter set. Call ModelBinder.setAdapter() before using model binding.'\n ) {\n super(message);\n this.name = 'AdapterNotSetError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n };\n }\n}\n\n/**\n * Error thrown when a model is invalid for an adapter\n */\nexport class InvalidModelError extends Error {\n public readonly model: unknown;\n\n constructor(message: string, model: unknown) {\n super(message);\n this.name = 'InvalidModelError';\n this.model = model;\n Error.captureStackTrace(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n model: typeof this.model === 'string' ? this.model : String(this.model),\n };\n }\n}\n\n/**\n * Error thrown when validation fails\n */\nexport class ValidationError extends Error {\n public readonly statusCode: number;\n public readonly details?: unknown;\n\n constructor(message: string, statusCode: number = 400, details?: unknown) {\n super(message);\n this.name = 'ValidationError';\n this.statusCode = statusCode;\n this.details = details;\n Error.captureStackTrace(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n statusCode: this.statusCode,\n details: this.details,\n };\n }\n}\n","import { IORMAdapter, QueryOptions, ModelMetadata, QueryModifier } from './types';\nimport { InvalidModelError } from '../errors';\n\n/**\n * Abstract base class providing common adapter functionality.\n * Extend this class to implement ORM-specific adapters.\n *\n * @typeParam TModel - Model type accepted by this adapter\n * @typeParam TResult - Result type returned by queries\n * @typeParam TQueryBuilder - ORM-specific query builder type\n */\nexport abstract class BaseAdapter<\n TModel = unknown,\n TResult = unknown,\n TQueryBuilder = unknown,\n> implements IORMAdapter<TModel, TResult> {\n abstract readonly name: string;\n\n abstract findByKey(\n model: TModel,\n key: string,\n value: unknown,\n options?: QueryOptions\n ): Promise<TResult | null>;\n\n abstract getPrimaryKeyName(model: TModel): string;\n\n abstract isValidModel(model: unknown): model is TModel;\n\n transformValue(_model: TModel, _key: string, value: string): unknown {\n if (/^\\d+$/.test(value)) {\n const num = parseInt(value, 10);\n if (!isNaN(num) && Number.isSafeInteger(num)) {\n return num;\n }\n }\n return value;\n }\n\n supportsSoftDeletes(_model: TModel): boolean {\n return false;\n }\n\n getModelMetadata(model: TModel): ModelMetadata {\n return {\n name: this.getModelName(model),\n primaryKey: this.getPrimaryKeyName(model),\n softDeletes: this.supportsSoftDeletes(model),\n adapter: this.name,\n };\n }\n\n protected validateModel(model: unknown): asserts model is TModel {\n if (!this.isValidModel(model)) {\n throw new InvalidModelError(`Invalid model for ${this.name} adapter`, model);\n }\n }\n\n protected getModelName(model: TModel): string {\n if (typeof model === 'string') {\n return model;\n }\n if (model && typeof model === 'object') {\n const obj = model as Record<string, unknown>;\n if (typeof obj.name === 'string') return obj.name;\n if (typeof obj.modelName === 'string') return obj.modelName;\n if (typeof obj.tableName === 'string') return obj.tableName;\n }\n if (model && typeof model === 'function') {\n return (model as { name: string }).name || 'Unknown';\n }\n return 'Unknown';\n }\n\n protected applySoftDeleteFilter(\n queryBuilder: TQueryBuilder,\n _options?: QueryOptions\n ): TQueryBuilder {\n return queryBuilder;\n }\n\n protected applyIncludes(\n queryBuilder: TQueryBuilder,\n _includes?: string[] | Record<string, unknown>\n ): TQueryBuilder {\n return queryBuilder;\n }\n\n protected applySelect(queryBuilder: TQueryBuilder, _select?: string[]): TQueryBuilder {\n return queryBuilder;\n }\n\n protected applyWhereConditions(\n queryBuilder: TQueryBuilder,\n _where?: Record<string, unknown>\n ): TQueryBuilder {\n return queryBuilder;\n }\n\n protected applyCustomQuery(\n queryBuilder: TQueryBuilder,\n queryFn?: QueryModifier<TQueryBuilder>\n ): TQueryBuilder {\n if (queryFn) {\n return queryFn(queryBuilder) as TQueryBuilder;\n }\n return queryBuilder;\n }\n}\n","/**\n * UUID regex pattern\n */\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * MongoDB ObjectId regex pattern (24 hex characters)\n */\nconst OBJECT_ID_REGEX = /^[0-9a-f]{24}$/i;\n\n/**\n * Check if a value is a valid UUID\n */\nexport function isUUID(value: string): boolean {\n return UUID_REGEX.test(value);\n}\n\n/**\n * Check if a value is a valid MongoDB ObjectId\n */\nexport function isObjectId(value: string): boolean {\n return OBJECT_ID_REGEX.test(value);\n}\n\n/**\n * Check if a value is a numeric string\n */\nexport function isNumeric(value: string): boolean {\n return /^-?\\d+$/.test(value);\n}\n\n/**\n * Check if a value is a valid positive integer\n */\nexport function isPositiveInteger(value: string): boolean {\n return /^\\d+$/.test(value) && parseInt(value, 10) > 0;\n}\n\n/**\n * Check if a value is a valid slug (lowercase alphanumeric with hyphens)\n */\nexport function isSlug(value: string): boolean {\n return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);\n}\n\n/**\n * Check if a value is a valid email\n */\nexport function isEmail(value: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value);\n}\n\n/**\n * Validate that a value is not empty\n */\nexport function isNotEmpty(value: unknown): boolean {\n if (value === null || value === undefined) {\n return false;\n }\n if (typeof value === 'string') {\n return value.trim().length > 0;\n }\n return true;\n}\n\n/**\n * Validate that a value is a non-empty string\n */\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\n/**\n * Validate that a value is a plain object\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Validate that a value is a function\n */\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport function isFunction(value: unknown): value is Function {\n return typeof value === 'function';\n}\n\n/**\n * Validate route parameter name\n */\nexport function isValidParamName(value: string): boolean {\n return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);\n}\n","import type {\n DataSource,\n Repository,\n EntityTarget,\n ObjectLiteral,\n SelectQueryBuilder,\n} from 'typeorm';\nimport { BaseAdapter } from '../core/BaseAdapter';\nimport { QueryOptions, ModelMetadata, QueryModifier } from '../core/types';\nimport { BindingError } from '../errors';\nimport { isUUID } from '../utils/validators';\n\n/**\n * TypeORM entity type alias for better readability\n */\nexport type TypeORMEntity = EntityTarget<ObjectLiteral>;\n\n/**\n * TypeORM find options structure\n */\ninterface TypeORMFindOptions {\n where: Record<string, unknown>;\n select?: Record<string, boolean>;\n relations?: string[];\n withDeleted?: boolean;\n}\n\n/**\n * Adapter for TypeORM supporting SQL and NoSQL databases\n */\nexport class TypeORMAdapter extends BaseAdapter<\n TypeORMEntity,\n ObjectLiteral,\n SelectQueryBuilder<ObjectLiteral>\n> {\n readonly name = 'typeorm';\n\n constructor(private dataSource: DataSource) {\n super();\n }\n\n getDataSource(): DataSource {\n return this.dataSource;\n }\n\n async findByKey(\n entity: TypeORMEntity,\n key: string,\n value: unknown,\n options: QueryOptions = {}\n ): Promise<ObjectLiteral | null> {\n this.validateModel(entity);\n\n try {\n const repository = this.dataSource.getRepository(entity);\n const metadata = repository.metadata;\n\n const transformedValue = this.transformValue(entity, key, value as string);\n\n if (options.query || options.lock || options.onlyTrashed) {\n return await this.findWithQueryBuilder(repository, key, transformedValue, options);\n }\n\n const findOptions: TypeORMFindOptions = {\n where: { [key]: transformedValue },\n };\n\n if (options.select && options.select.length > 0) {\n findOptions.select = options.select.reduce<Record<string, boolean>>((acc, field) => {\n acc[field] = true;\n return acc;\n }, {});\n }\n\n if (options.include) {\n findOptions.relations = Array.isArray(options.include)\n ? options.include\n : Object.keys(options.include);\n }\n\n if (options.where) {\n findOptions.where = {\n ...findOptions.where,\n ...options.where,\n };\n }\n\n if (options.withTrashed && metadata.deleteDateColumn) {\n findOptions.withDeleted = true;\n }\n\n const result = await repository.findOne(findOptions);\n return result;\n } catch (error) {\n throw new BindingError(`Failed to fetch entity: ${(error as Error).message}`, error as Error);\n }\n }\n\n getPrimaryKeyName(entity: TypeORMEntity): string {\n try {\n const repository = this.dataSource.getRepository(entity);\n const metadata = repository.metadata;\n\n if (metadata.primaryColumns.length > 0) {\n return metadata.primaryColumns[0].propertyName;\n }\n } catch {\n // Fall through to default\n }\n return 'id';\n }\n\n isValidModel(model: unknown): model is TypeORMEntity {\n try {\n this.dataSource.getRepository(model as TypeORMEntity);\n return true;\n } catch {\n return false;\n }\n }\n\n transformValue(entity: TypeORMEntity, key: string, value: string): unknown {\n try {\n const repository = this.dataSource.getRepository(entity);\n const metadata = repository.metadata;\n const column = metadata.findColumnWithPropertyName(key);\n\n if (!column) {\n if (isUUID(value)) {\n return value;\n }\n const num = parseInt(value, 10);\n if (!isNaN(num) && num.toString() === value) {\n return num;\n }\n return value;\n }\n\n const columnType = String(column.type).toLowerCase();\n\n if (['int', 'integer', 'smallint', 'bigint', 'number'].includes(columnType)) {\n const num = parseInt(value, 10);\n return isNaN(num) ? value : num;\n }\n\n if (columnType === 'uuid') {\n return value;\n }\n\n if (columnType === 'boolean' || columnType === 'bool') {\n return value === 'true' || value === '1';\n }\n\n return value;\n } catch {\n return value;\n }\n }\n\n supportsSoftDeletes(entity: TypeORMEntity): boolean {\n try {\n const repository = this.dataSource.getRepository(entity);\n const metadata = repository.metadata;\n return !!metadata.deleteDateColumn;\n } catch {\n return false;\n }\n }\n\n getModelMetadata(entity: TypeORMEntity): ModelMetadata {\n try {\n const repository = this.dataSource.getRepository(entity);\n const metadata = repository.metadata;\n\n return {\n name: metadata.name,\n primaryKey: this.getPrimaryKeyName(entity),\n tableName: metadata.tableName,\n softDeletes: this.supportsSoftDeletes(entity),\n relations: metadata.relations.map(r => r.propertyName),\n adapter: this.name,\n };\n } catch {\n return {\n name: 'Unknown',\n primaryKey: 'id',\n softDeletes: false,\n adapter: this.name,\n };\n }\n }\n\n private async findWithQueryBuilder(\n repository: Repository<ObjectLiteral>,\n key: string,\n value: unknown,\n options: QueryOptions\n ): Promise<ObjectLiteral | null> {\n const metadata = repository.metadata;\n const alias = metadata.name.toLowerCase();\n\n let queryBuilder = repository\n .createQueryBuilder(alias)\n .where(`${alias}.${key} = :value`, { value });\n\n if (options.include) {\n const relations = Array.isArray(options.include)\n ? options.include\n : Object.keys(options.include);\n\n relations.forEach(relation => {\n queryBuilder = queryBuilder.leftJoinAndSelect(`${alias}.${relation}`, relation);\n });\n }\n\n if (options.where) {\n Object.entries(options.where).forEach(([field, val]) => {\n queryBuilder = queryBuilder.andWhere(`${alias}.${field} = :${field}`, { [field]: val });\n });\n }\n\n if (options.select && options.select.length > 0) {\n queryBuilder = queryBuilder.select(options.select.map(field => `${alias}.${field}`));\n }\n\n if (options.withTrashed) {\n queryBuilder = queryBuilder.withDeleted();\n } else if (options.onlyTrashed && metadata.deleteDateColumn) {\n queryBuilder = queryBuilder\n .withDeleted()\n .andWhere(`${alias}.${metadata.deleteDateColumn.propertyName} IS NOT NULL`);\n }\n\n if (options.lock === 'forUpdate') {\n queryBuilder = queryBuilder.setLock('pessimistic_write');\n } else if (options.lock === 'forShare') {\n queryBuilder = queryBuilder.setLock('pessimistic_read');\n }\n\n if (options.query) {\n queryBuilder = this.applyCustomQuery(\n queryBuilder,\n options.query as QueryModifier<SelectQueryBuilder<ObjectLiteral>>\n );\n }\n\n return await queryBuilder.getOne();\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,411 @@
1
+ import { Request, Response, RequestHandler } from 'express';
2
+ import { I as IORMAdapter, B as BindOptions, a as BindingResult, M as ModelBindingsConfig } from './BaseAdapter-BjvLQijd.mjs';
3
+ export { b as BaseAdapter, c as BindingContext, C as CacheEntry, E as ExtractModelType, g as MiddlewareFunction, e as ModelBindingGlobalConfig, d as ModelMetadata, Q as QueryOptions, T as TypedRequest, f as TypedRequestHandler } from './BaseAdapter-BjvLQijd.mjs';
4
+ export { KnexAdapter, KnexModel, defineKnexModel } from './adapters/KnexAdapter.mjs';
5
+ export { MongooseAdapter } from './adapters/MongooseAdapter.mjs';
6
+ export { TypeORMAdapter } from './adapters/TypeORMAdapter.mjs';
7
+ export { SequelizeAdapter } from './adapters/SequelizeAdapter.mjs';
8
+ export { PrismaAdapter } from './adapters/PrismaAdapter.mjs';
9
+ import 'knex';
10
+ import 'mongoose';
11
+ import 'typeorm';
12
+ import 'sequelize';
13
+
14
+ /**
15
+ * Central model binding orchestrator
16
+ */
17
+ declare class ModelBinder {
18
+ private static adapter;
19
+ private static cache;
20
+ private static debug;
21
+ static setAdapter(adapter: IORMAdapter): void;
22
+ static getAdapter(): IORMAdapter;
23
+ static hasAdapter(): boolean;
24
+ static clearAdapter(): void;
25
+ static setDebug(enabled: boolean): void;
26
+ static isDebugEnabled(): boolean;
27
+ static clearCache(): void;
28
+ static getCacheStats(): {
29
+ size: number;
30
+ maxSize: number;
31
+ };
32
+ static bind(req: Request, res: Response, paramName: string, model: unknown, options?: BindOptions): Promise<BindingResult>;
33
+ private static attachToRequest;
34
+ private static getCacheKey;
35
+ private static getModelName;
36
+ static reset(): void;
37
+ }
38
+
39
+ /**
40
+ * Create middleware that binds a model to a route parameter
41
+ *
42
+ * @param paramName - The route parameter name (without ':')
43
+ * @param model - The model class/schema/table
44
+ * @param options - Binding options
45
+ * @returns Express middleware function
46
+ *
47
+ * @example
48
+ * app.get('/users/:user', bindModel('user', User), (req, res) => {
49
+ * res.json(req.user);
50
+ * });
51
+ */
52
+ declare function bindModel(paramName: string, model: unknown, options?: BindOptions): RequestHandler;
53
+ /**
54
+ * Bind multiple models in a single middleware
55
+ *
56
+ * @param bindings - Object mapping parameter names to model configs
57
+ * @returns Express middleware function
58
+ *
59
+ * @example
60
+ * app.get('/users/:user/posts/:post',
61
+ * bindModels({
62
+ * user: { model: User },
63
+ * post: { model: Post, options: { include: ['comments'] } }
64
+ * }),
65
+ * (req, res) => {
66
+ * res.json({ user: req.user, post: req.post });
67
+ * }
68
+ * );
69
+ */
70
+ declare function bindModels(bindings: ModelBindingsConfig): RequestHandler;
71
+ /**
72
+ * Bind a model optionally (don't throw 404 if not found)
73
+ *
74
+ * @param paramName - The route parameter name
75
+ * @param model - The model class/schema/table
76
+ * @param options - Binding options
77
+ * @returns Express middleware function
78
+ *
79
+ * @example
80
+ * app.get('/posts/:post?', bindOptional('post', Post), (req, res) => {
81
+ * if (req.post) {
82
+ * res.json(req.post);
83
+ * } else {
84
+ * res.json({ message: 'No specific post' });
85
+ * }
86
+ * });
87
+ */
88
+ declare function bindOptional(paramName: string, model: unknown, options?: BindOptions): RequestHandler;
89
+ /**
90
+ * Create a middleware that binds a model using a custom key
91
+ *
92
+ * @param paramName - The route parameter name
93
+ * @param model - The model class/schema/table
94
+ * @param key - The field to search by
95
+ * @param options - Additional binding options
96
+ * @returns Express middleware function
97
+ *
98
+ * @example
99
+ * app.get('/users/by-email/:email', bindByKey('email', User, 'email'), (req, res) => {
100
+ * res.json(req.email);
101
+ * });
102
+ */
103
+ declare function bindByKey(paramName: string, model: unknown, key: string, options?: BindOptions): RequestHandler;
104
+ /**
105
+ * Create a middleware that binds a model and attaches it with a custom name
106
+ *
107
+ * @param paramName - The route parameter name
108
+ * @param model - The model class/schema/table
109
+ * @param attachAs - The name to attach the model as on the request
110
+ * @param options - Additional binding options
111
+ * @returns Express middleware function
112
+ *
113
+ * @example
114
+ * app.get('/users/:id', bindAs('id', User, 'currentUser'), (req, res) => {
115
+ * res.json(req.currentUser);
116
+ * });
117
+ */
118
+ declare function bindAs(paramName: string, model: unknown, attachAs: string, options?: BindOptions): RequestHandler;
119
+ /**
120
+ * Create a middleware that binds a model with caching
121
+ *
122
+ * @param paramName - The route parameter name
123
+ * @param model - The model class/schema/table
124
+ * @param ttl - Cache TTL in milliseconds (default: 60000)
125
+ * @param options - Additional binding options
126
+ * @returns Express middleware function
127
+ *
128
+ * @example
129
+ * app.get('/users/:user', bindCached('user', User, 300000), (req, res) => {
130
+ * res.json(req.user);
131
+ * });
132
+ */
133
+ declare function bindCached(paramName: string, model: unknown, ttl?: number, options?: BindOptions): RequestHandler;
134
+ /**
135
+ * Create a middleware that binds a model with eager-loaded relations
136
+ *
137
+ * @param paramName - The route parameter name
138
+ * @param model - The model class/schema/table
139
+ * @param relations - Relations to eager load
140
+ * @param options - Additional binding options
141
+ * @returns Express middleware function
142
+ *
143
+ * @example
144
+ * app.get('/users/:user', bindWithRelations('user', User, ['posts', 'profile']), (req, res) => {
145
+ * res.json(req.user);
146
+ * });
147
+ */
148
+ declare function bindWithRelations(paramName: string, model: unknown, relations: string[] | Record<string, unknown>, options?: BindOptions): RequestHandler;
149
+
150
+ /**
151
+ * Base error class for model binding errors
152
+ */
153
+ declare class BindingError extends Error {
154
+ readonly originalError?: Error;
155
+ constructor(message: string, originalError?: Error);
156
+ toJSON(): Record<string, unknown>;
157
+ }
158
+ /**
159
+ * Error thrown when a model is not found (404)
160
+ */
161
+ declare class ModelNotFoundError extends Error {
162
+ readonly statusCode = 404;
163
+ readonly paramName: string;
164
+ readonly paramValue: string;
165
+ readonly modelName: string;
166
+ constructor(paramName: string, paramValue: string, modelName: string, customMessage?: string);
167
+ toJSON(): Record<string, unknown>;
168
+ }
169
+ /**
170
+ * Error thrown when no adapter is configured
171
+ */
172
+ declare class AdapterNotSetError extends Error {
173
+ constructor(message?: string);
174
+ toJSON(): Record<string, unknown>;
175
+ }
176
+ /**
177
+ * Error thrown when a model is invalid for an adapter
178
+ */
179
+ declare class InvalidModelError extends Error {
180
+ readonly model: unknown;
181
+ constructor(message: string, model: unknown);
182
+ toJSON(): Record<string, unknown>;
183
+ }
184
+ /**
185
+ * Error thrown when validation fails
186
+ */
187
+ declare class ValidationError extends Error {
188
+ readonly statusCode: number;
189
+ readonly details?: unknown;
190
+ constructor(message: string, statusCode?: number, details?: unknown);
191
+ toJSON(): Record<string, unknown>;
192
+ }
193
+
194
+ /**
195
+ * Simple in-memory cache for model binding
196
+ */
197
+ declare class Cache {
198
+ private store;
199
+ private maxSize;
200
+ constructor(maxSize?: number);
201
+ /**
202
+ * Get a value from cache
203
+ */
204
+ get<T = unknown>(key: string): T | null;
205
+ /**
206
+ * Set a value in cache
207
+ */
208
+ set<T = unknown>(key: string, value: T, ttl: number): void;
209
+ /**
210
+ * Delete a value from cache
211
+ */
212
+ delete(key: string): boolean;
213
+ /**
214
+ * Clear all cached values
215
+ */
216
+ clear(): void;
217
+ /**
218
+ * Get cache size
219
+ */
220
+ get size(): number;
221
+ /**
222
+ * Check if key exists and is not expired
223
+ */
224
+ has(key: string): boolean;
225
+ /**
226
+ * Get all keys in the cache
227
+ */
228
+ keys(): string[];
229
+ /**
230
+ * Remove expired entries
231
+ */
232
+ prune(): number;
233
+ /**
234
+ * Get cache statistics
235
+ */
236
+ getStats(): {
237
+ size: number;
238
+ maxSize: number;
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Log levels for the logger
244
+ */
245
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
246
+ /**
247
+ * Logger configuration
248
+ */
249
+ interface LoggerConfig {
250
+ enabled: boolean;
251
+ level: LogLevel;
252
+ prefix: string;
253
+ }
254
+ /**
255
+ * Simple logger utility for debugging
256
+ */
257
+ declare class Logger {
258
+ private config;
259
+ private levelPriority;
260
+ /**
261
+ * Enable debug logging
262
+ */
263
+ enable(): void;
264
+ /**
265
+ * Disable debug logging
266
+ */
267
+ disable(): void;
268
+ /**
269
+ * Check if logging is enabled
270
+ */
271
+ isEnabled(): boolean;
272
+ /**
273
+ * Set log level
274
+ */
275
+ setLevel(level: LogLevel): void;
276
+ /**
277
+ * Get current log level
278
+ */
279
+ getLevel(): LogLevel;
280
+ /**
281
+ * Set custom prefix
282
+ */
283
+ setPrefix(prefix: string): void;
284
+ /**
285
+ * Check if a message at the given level should be logged
286
+ */
287
+ private shouldLog;
288
+ /**
289
+ * Format log message
290
+ */
291
+ private formatMessage;
292
+ /**
293
+ * Log a debug message
294
+ */
295
+ debug(message: string, context?: unknown): void;
296
+ /**
297
+ * Log an info message
298
+ */
299
+ info(message: string, context?: unknown): void;
300
+ /**
301
+ * Log a warning message
302
+ */
303
+ warn(message: string, context?: unknown): void;
304
+ /**
305
+ * Log an error message
306
+ */
307
+ error(message: string, error?: unknown): void;
308
+ /**
309
+ * Reset logger to default configuration
310
+ */
311
+ reset(): void;
312
+ /**
313
+ * Get current configuration
314
+ */
315
+ getConfig(): LoggerConfig;
316
+ }
317
+ declare const logger: Logger;
318
+
319
+ /**
320
+ * Check if a value is a valid UUID
321
+ */
322
+ declare function isUUID(value: string): boolean;
323
+ /**
324
+ * Check if a value is a valid MongoDB ObjectId
325
+ */
326
+ declare function isObjectId(value: string): boolean;
327
+ /**
328
+ * Check if a value is a numeric string
329
+ */
330
+ declare function isNumeric(value: string): boolean;
331
+ /**
332
+ * Check if a value is a valid positive integer
333
+ */
334
+ declare function isPositiveInteger(value: string): boolean;
335
+ /**
336
+ * Check if a value is a valid slug (lowercase alphanumeric with hyphens)
337
+ */
338
+ declare function isSlug(value: string): boolean;
339
+ /**
340
+ * Check if a value is a valid email
341
+ */
342
+ declare function isEmail(value: string): boolean;
343
+ /**
344
+ * Validate that a value is not empty
345
+ */
346
+ declare function isNotEmpty(value: unknown): boolean;
347
+ /**
348
+ * Validate that a value is a non-empty string
349
+ */
350
+ declare function isNonEmptyString(value: unknown): value is string;
351
+ /**
352
+ * Validate that a value is a plain object
353
+ */
354
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
355
+ /**
356
+ * Validate that a value is a function
357
+ */
358
+ declare function isFunction(value: unknown): value is Function;
359
+ /**
360
+ * Validate route parameter name
361
+ */
362
+ declare function isValidParamName(value: string): boolean;
363
+
364
+ /**
365
+ * Transform a string value to a number if it's numeric
366
+ */
367
+ declare function toNumber(value: string): number | string;
368
+ /**
369
+ * Transform a string value to a float if it's a valid decimal
370
+ */
371
+ declare function toFloat(value: string): number | string;
372
+ /**
373
+ * Transform a string value to a boolean
374
+ */
375
+ declare function toBoolean(value: string): boolean;
376
+ /**
377
+ * Transform a string to lowercase
378
+ */
379
+ declare function toLowerCase(value: string): string;
380
+ /**
381
+ * Transform a string to uppercase
382
+ */
383
+ declare function toUpperCase(value: string): string;
384
+ /**
385
+ * Trim whitespace from a string
386
+ */
387
+ declare function trim(value: string): string;
388
+ /**
389
+ * Transform a slug to underscore format
390
+ */
391
+ declare function slugToUnderscore(value: string): string;
392
+ /**
393
+ * Transform underscores to dashes (slug format)
394
+ */
395
+ declare function underscoreToSlug(value: string): string;
396
+ /**
397
+ * Auto-detect and transform value based on format
398
+ */
399
+ declare function autoTransform(value: string): unknown;
400
+ /**
401
+ * Create a composed transformer from multiple transform functions
402
+ */
403
+ declare function compose(...transformers: Array<(value: string) => unknown>): (value: string) => unknown;
404
+ /**
405
+ * Identity transformer - returns the value unchanged
406
+ */
407
+ declare function identity<T>(value: T): T;
408
+
409
+ declare const VERSION = "1.0.0";
410
+
411
+ export { AdapterNotSetError, BindOptions, BindingError, BindingResult, Cache, IORMAdapter, InvalidModelError, type LogLevel, type LoggerConfig, ModelBinder, ModelBindingsConfig, ModelNotFoundError, VERSION, ValidationError, autoTransform, bindAs, bindByKey, bindCached, bindModel, bindModels, bindOptional, bindWithRelations, compose, identity, isEmail, isFunction, isNonEmptyString, isNotEmpty, isNumeric, isObjectId, isPlainObject, isPositiveInteger, isSlug, isUUID, isValidParamName, logger, slugToUnderscore, toBoolean, toFloat, toLowerCase, toNumber, toUpperCase, trim, underscoreToSlug };