express-model-binding 1.0.0 → 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.
Files changed (51) hide show
  1. package/dist/{BaseAdapter-BjvLQijd.d.mts → BaseAdapter-Dr9a8ZUY.d.mts} +17 -1
  2. package/dist/{BaseAdapter-BjvLQijd.d.ts → BaseAdapter-Dr9a8ZUY.d.ts} +17 -1
  3. package/dist/adapters/KnexAdapter.d.mts +1 -1
  4. package/dist/adapters/KnexAdapter.d.ts +1 -1
  5. package/dist/adapters/KnexAdapter.js +1 -257
  6. package/dist/adapters/KnexAdapter.mjs +1 -229
  7. package/dist/adapters/MongooseAdapter.d.mts +1 -1
  8. package/dist/adapters/MongooseAdapter.d.ts +1 -1
  9. package/dist/adapters/MongooseAdapter.js +1 -245
  10. package/dist/adapters/MongooseAdapter.mjs +1 -225
  11. package/dist/adapters/PrismaAdapter.d.mts +1 -1
  12. package/dist/adapters/PrismaAdapter.d.ts +1 -1
  13. package/dist/adapters/PrismaAdapter.js +1 -247
  14. package/dist/adapters/PrismaAdapter.mjs +1 -220
  15. package/dist/adapters/SequelizeAdapter.d.mts +1 -1
  16. package/dist/adapters/SequelizeAdapter.d.ts +1 -1
  17. package/dist/adapters/SequelizeAdapter.js +1 -280
  18. package/dist/adapters/SequelizeAdapter.mjs +1 -260
  19. package/dist/adapters/TypeORMAdapter.d.mts +1 -1
  20. package/dist/adapters/TypeORMAdapter.d.ts +1 -1
  21. package/dist/adapters/TypeORMAdapter.js +1 -294
  22. package/dist/adapters/TypeORMAdapter.mjs +1 -267
  23. package/dist/chunk-7I3EYD6K.js +1 -0
  24. package/dist/chunk-7OR75DD2.mjs +1 -0
  25. package/dist/chunk-A7LUHNSI.mjs +1 -0
  26. package/dist/chunk-B3DBSP2J.mjs +1 -0
  27. package/dist/chunk-CKT6LD2K.js +1 -0
  28. package/dist/chunk-DBYMXDVA.mjs +1 -0
  29. package/dist/chunk-EEPSARTF.js +1 -0
  30. package/dist/chunk-ISSVD3WP.js +1 -0
  31. package/dist/chunk-LLYCNTZ7.mjs +1 -0
  32. package/dist/chunk-OKPAWWZN.js +1 -0
  33. package/dist/chunk-Z6WU7X3A.mjs +1 -0
  34. package/dist/chunk-ZJ2WW53U.js +1 -0
  35. package/dist/index.d.mts +4 -3
  36. package/dist/index.d.ts +4 -3
  37. package/dist/index.js +1 -1514
  38. package/dist/index.mjs +1 -1450
  39. package/package.json +5 -3
  40. package/dist/adapters/KnexAdapter.js.map +0 -1
  41. package/dist/adapters/KnexAdapter.mjs.map +0 -1
  42. package/dist/adapters/MongooseAdapter.js.map +0 -1
  43. package/dist/adapters/MongooseAdapter.mjs.map +0 -1
  44. package/dist/adapters/PrismaAdapter.js.map +0 -1
  45. package/dist/adapters/PrismaAdapter.mjs.map +0 -1
  46. package/dist/adapters/SequelizeAdapter.js.map +0 -1
  47. package/dist/adapters/SequelizeAdapter.mjs.map +0 -1
  48. package/dist/adapters/TypeORMAdapter.js.map +0 -1
  49. package/dist/adapters/TypeORMAdapter.mjs.map +0 -1
  50. package/dist/index.js.map +0 -1
  51. package/dist/index.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -1,1514 +1 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
23
- AdapterNotSetError: () => AdapterNotSetError,
24
- BaseAdapter: () => BaseAdapter,
25
- BindingError: () => BindingError,
26
- Cache: () => Cache,
27
- InvalidModelError: () => InvalidModelError,
28
- KnexAdapter: () => KnexAdapter,
29
- ModelBinder: () => ModelBinder,
30
- ModelNotFoundError: () => ModelNotFoundError,
31
- MongooseAdapter: () => MongooseAdapter,
32
- PrismaAdapter: () => PrismaAdapter,
33
- SequelizeAdapter: () => SequelizeAdapter,
34
- TypeORMAdapter: () => TypeORMAdapter,
35
- VERSION: () => VERSION,
36
- ValidationError: () => ValidationError,
37
- autoTransform: () => autoTransform,
38
- bindAs: () => bindAs,
39
- bindByKey: () => bindByKey,
40
- bindCached: () => bindCached,
41
- bindModel: () => bindModel,
42
- bindModels: () => bindModels,
43
- bindOptional: () => bindOptional,
44
- bindWithRelations: () => bindWithRelations,
45
- compose: () => compose,
46
- defineKnexModel: () => defineKnexModel,
47
- identity: () => identity,
48
- isEmail: () => isEmail,
49
- isFunction: () => isFunction,
50
- isNonEmptyString: () => isNonEmptyString,
51
- isNotEmpty: () => isNotEmpty,
52
- isNumeric: () => isNumeric,
53
- isObjectId: () => isObjectId,
54
- isPlainObject: () => isPlainObject,
55
- isPositiveInteger: () => isPositiveInteger,
56
- isSlug: () => isSlug,
57
- isUUID: () => isUUID,
58
- isValidParamName: () => isValidParamName,
59
- logger: () => logger,
60
- slugToUnderscore: () => slugToUnderscore,
61
- toBoolean: () => toBoolean,
62
- toFloat: () => toFloat,
63
- toLowerCase: () => toLowerCase,
64
- toNumber: () => toNumber,
65
- toUpperCase: () => toUpperCase,
66
- trim: () => trim,
67
- underscoreToSlug: () => underscoreToSlug
68
- });
69
- module.exports = __toCommonJS(src_exports);
70
-
71
- // src/errors/index.ts
72
- var BindingError = class extends Error {
73
- constructor(message, originalError) {
74
- super(message);
75
- this.name = "BindingError";
76
- this.originalError = originalError;
77
- Error.captureStackTrace(this, this.constructor);
78
- }
79
- toJSON() {
80
- return {
81
- name: this.name,
82
- message: this.message,
83
- originalError: this.originalError?.message
84
- };
85
- }
86
- };
87
- var ModelNotFoundError = class extends Error {
88
- constructor(paramName, paramValue, modelName, customMessage) {
89
- super(customMessage || `${modelName} not found with ${paramName} = ${paramValue}`);
90
- this.statusCode = 404;
91
- this.name = "ModelNotFoundError";
92
- this.paramName = paramName;
93
- this.paramValue = paramValue;
94
- this.modelName = modelName;
95
- Error.captureStackTrace(this, this.constructor);
96
- }
97
- toJSON() {
98
- return {
99
- error: "Not Found",
100
- message: this.message,
101
- statusCode: this.statusCode,
102
- param: this.paramName,
103
- value: this.paramValue,
104
- model: this.modelName
105
- };
106
- }
107
- };
108
- var AdapterNotSetError = class extends Error {
109
- constructor(message = "No adapter set. Call ModelBinder.setAdapter() before using model binding.") {
110
- super(message);
111
- this.name = "AdapterNotSetError";
112
- Error.captureStackTrace(this, this.constructor);
113
- }
114
- toJSON() {
115
- return {
116
- name: this.name,
117
- message: this.message
118
- };
119
- }
120
- };
121
- var InvalidModelError = class extends Error {
122
- constructor(message, model) {
123
- super(message);
124
- this.name = "InvalidModelError";
125
- this.model = model;
126
- Error.captureStackTrace(this, this.constructor);
127
- }
128
- toJSON() {
129
- return {
130
- name: this.name,
131
- message: this.message,
132
- model: typeof this.model === "string" ? this.model : String(this.model)
133
- };
134
- }
135
- };
136
- var ValidationError = class extends Error {
137
- constructor(message, statusCode = 400, details) {
138
- super(message);
139
- this.name = "ValidationError";
140
- this.statusCode = statusCode;
141
- this.details = details;
142
- Error.captureStackTrace(this, this.constructor);
143
- }
144
- toJSON() {
145
- return {
146
- name: this.name,
147
- message: this.message,
148
- statusCode: this.statusCode,
149
- details: this.details
150
- };
151
- }
152
- };
153
-
154
- // src/utils/cache.ts
155
- var Cache = class {
156
- constructor(maxSize = 1e3) {
157
- this.store = /* @__PURE__ */ new Map();
158
- this.maxSize = maxSize;
159
- }
160
- /**
161
- * Get a value from cache
162
- */
163
- get(key) {
164
- const entry = this.store.get(key);
165
- if (!entry) {
166
- return null;
167
- }
168
- if (Date.now() - entry.timestamp > entry.ttl) {
169
- this.store.delete(key);
170
- return null;
171
- }
172
- return entry.value;
173
- }
174
- /**
175
- * Set a value in cache
176
- */
177
- set(key, value, ttl) {
178
- if (this.store.size >= this.maxSize) {
179
- const firstKey = this.store.keys().next().value;
180
- if (firstKey) {
181
- this.store.delete(firstKey);
182
- }
183
- }
184
- this.store.set(key, {
185
- value,
186
- timestamp: Date.now(),
187
- ttl
188
- });
189
- }
190
- /**
191
- * Delete a value from cache
192
- */
193
- delete(key) {
194
- return this.store.delete(key);
195
- }
196
- /**
197
- * Clear all cached values
198
- */
199
- clear() {
200
- this.store.clear();
201
- }
202
- /**
203
- * Get cache size
204
- */
205
- get size() {
206
- return this.store.size;
207
- }
208
- /**
209
- * Check if key exists and is not expired
210
- */
211
- has(key) {
212
- return this.get(key) !== null;
213
- }
214
- /**
215
- * Get all keys in the cache
216
- */
217
- keys() {
218
- return Array.from(this.store.keys());
219
- }
220
- /**
221
- * Remove expired entries
222
- */
223
- prune() {
224
- let removed = 0;
225
- const now = Date.now();
226
- for (const [key, entry] of this.store.entries()) {
227
- if (now - entry.timestamp > entry.ttl) {
228
- this.store.delete(key);
229
- removed++;
230
- }
231
- }
232
- return removed;
233
- }
234
- /**
235
- * Get cache statistics
236
- */
237
- getStats() {
238
- return {
239
- size: this.store.size,
240
- maxSize: this.maxSize
241
- };
242
- }
243
- };
244
-
245
- // src/utils/logger.ts
246
- var Logger = class {
247
- constructor() {
248
- this.config = {
249
- enabled: false,
250
- level: "info",
251
- prefix: "[express-model-binding]"
252
- };
253
- this.levelPriority = {
254
- debug: 0,
255
- info: 1,
256
- warn: 2,
257
- error: 3
258
- };
259
- }
260
- /**
261
- * Enable debug logging
262
- */
263
- enable() {
264
- this.config.enabled = true;
265
- }
266
- /**
267
- * Disable debug logging
268
- */
269
- disable() {
270
- this.config.enabled = false;
271
- }
272
- /**
273
- * Check if logging is enabled
274
- */
275
- isEnabled() {
276
- return this.config.enabled;
277
- }
278
- /**
279
- * Set log level
280
- */
281
- setLevel(level) {
282
- this.config.level = level;
283
- }
284
- /**
285
- * Get current log level
286
- */
287
- getLevel() {
288
- return this.config.level;
289
- }
290
- /**
291
- * Set custom prefix
292
- */
293
- setPrefix(prefix) {
294
- this.config.prefix = prefix;
295
- }
296
- /**
297
- * Check if a message at the given level should be logged
298
- */
299
- shouldLog(level) {
300
- if (!this.config.enabled) {
301
- return false;
302
- }
303
- return this.levelPriority[level] >= this.levelPriority[this.config.level];
304
- }
305
- /**
306
- * Format log message
307
- */
308
- formatMessage(level, message) {
309
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
310
- return `${this.config.prefix} [${timestamp}] [${level.toUpperCase()}] ${message}`;
311
- }
312
- /**
313
- * Log a debug message
314
- */
315
- debug(message, context) {
316
- if (this.shouldLog("debug")) {
317
- console.log(this.formatMessage("debug", message), context !== void 0 ? context : "");
318
- }
319
- }
320
- /**
321
- * Log an info message
322
- */
323
- info(message, context) {
324
- if (this.shouldLog("info")) {
325
- console.info(this.formatMessage("info", message), context !== void 0 ? context : "");
326
- }
327
- }
328
- /**
329
- * Log a warning message
330
- */
331
- warn(message, context) {
332
- if (this.shouldLog("warn")) {
333
- console.warn(this.formatMessage("warn", message), context !== void 0 ? context : "");
334
- }
335
- }
336
- /**
337
- * Log an error message
338
- */
339
- error(message, error) {
340
- if (this.shouldLog("error")) {
341
- console.error(this.formatMessage("error", message), error !== void 0 ? error : "");
342
- }
343
- }
344
- /**
345
- * Reset logger to default configuration
346
- */
347
- reset() {
348
- this.config = {
349
- enabled: false,
350
- level: "info",
351
- prefix: "[express-model-binding]"
352
- };
353
- }
354
- /**
355
- * Get current configuration
356
- */
357
- getConfig() {
358
- return { ...this.config };
359
- }
360
- };
361
- var logger = new Logger();
362
-
363
- // src/core/ModelBinder.ts
364
- var ModelBinder = class {
365
- static setAdapter(adapter) {
366
- if (!adapter) {
367
- throw new Error("Adapter cannot be null or undefined");
368
- }
369
- this.adapter = adapter;
370
- logger.debug(`Adapter set: ${adapter.name}`);
371
- }
372
- static getAdapter() {
373
- if (!this.adapter) {
374
- throw new AdapterNotSetError(
375
- "No adapter set. Call ModelBinder.setAdapter() before using model binding."
376
- );
377
- }
378
- return this.adapter;
379
- }
380
- static hasAdapter() {
381
- return this.adapter !== null;
382
- }
383
- static clearAdapter() {
384
- this.adapter = null;
385
- logger.debug("Adapter cleared");
386
- }
387
- static setDebug(enabled) {
388
- this.debug = enabled;
389
- if (enabled) {
390
- logger.enable();
391
- } else {
392
- logger.disable();
393
- }
394
- }
395
- static isDebugEnabled() {
396
- return this.debug;
397
- }
398
- static clearCache() {
399
- this.cache.clear();
400
- logger.debug("Cache cleared");
401
- }
402
- static getCacheStats() {
403
- return this.cache.getStats();
404
- }
405
- static async bind(req, res, paramName, model, options = {}) {
406
- const startTime = Date.now();
407
- const adapter = this.getAdapter();
408
- const paramValue = req.params[paramName];
409
- const context = {
410
- req,
411
- res,
412
- paramName,
413
- paramValue,
414
- model,
415
- options,
416
- adapter,
417
- startTime
418
- };
419
- try {
420
- if (paramValue === void 0 || paramValue === null || paramValue === "") {
421
- if (options.optional) {
422
- const attachAs2 = options.as || paramName;
423
- this.attachToRequest(req, attachAs2, void 0);
424
- return {
425
- success: true,
426
- model: void 0,
427
- duration: Date.now() - startTime
428
- };
429
- }
430
- throw new BindingError(
431
- `Parameter '${paramName}' is required but was not provided`,
432
- new Error("Missing parameter")
433
- );
434
- }
435
- if (!adapter.isValidModel(model)) {
436
- throw new BindingError(
437
- `Invalid model for ${adapter.name} adapter`,
438
- new Error("Model validation failed")
439
- );
440
- }
441
- const key = options.key || adapter.getPrimaryKeyName(model);
442
- let value = context.paramValue;
443
- if (options.transformValue) {
444
- value = options.transformValue(paramValue);
445
- } else {
446
- value = adapter.transformValue(model, key, paramValue);
447
- }
448
- const cacheKey = this.getCacheKey(model, key, value, options);
449
- if (options.cache) {
450
- const cached = this.cache.get(cacheKey);
451
- if (cached !== null) {
452
- logger.debug(`Cache hit for ${paramName}:${value}`);
453
- const attachAs2 = options.as || paramName;
454
- this.attachToRequest(req, attachAs2, cached);
455
- return {
456
- success: true,
457
- model: cached,
458
- duration: Date.now() - startTime,
459
- fromCache: true
460
- };
461
- }
462
- }
463
- logger.debug(`Fetching ${paramName}:${value} using ${adapter.name}`);
464
- const result = await adapter.findByKey(model, key, value, options);
465
- if (!result) {
466
- if (options.optional) {
467
- const attachAs2 = options.as || paramName;
468
- this.attachToRequest(req, attachAs2, void 0);
469
- return {
470
- success: true,
471
- model: void 0,
472
- duration: Date.now() - startTime
473
- };
474
- }
475
- let error;
476
- if (options.errorMessage) {
477
- error = new ModelNotFoundError(
478
- paramName,
479
- String(value),
480
- this.getModelName(model),
481
- options.errorMessage
482
- );
483
- } else if (options.onNotFound) {
484
- error = typeof options.onNotFound === "function" ? options.onNotFound(paramName, String(value)) : options.onNotFound;
485
- } else {
486
- error = new ModelNotFoundError(paramName, String(value), this.getModelName(model));
487
- }
488
- throw error;
489
- }
490
- if (options.validate) {
491
- await options.validate(result, req);
492
- }
493
- if (options.cache) {
494
- const ttl = options.cacheTTL || (typeof options.cache === "number" ? options.cache : 6e4);
495
- this.cache.set(cacheKey, result, ttl);
496
- logger.debug(`Cached ${paramName}:${value} for ${ttl}ms`);
497
- }
498
- const attachAs = options.as || paramName;
499
- this.attachToRequest(req, attachAs, result);
500
- logger.debug(`Successfully bound ${paramName}:${value}`);
501
- return {
502
- success: true,
503
- model: result,
504
- duration: Date.now() - startTime,
505
- fromCache: false
506
- };
507
- } catch (error) {
508
- logger.error(`Failed to bind ${paramName}:${context.paramValue}`, error);
509
- return {
510
- success: false,
511
- error,
512
- duration: Date.now() - startTime
513
- };
514
- }
515
- }
516
- static attachToRequest(req, key, value) {
517
- req[key] = value;
518
- }
519
- static getCacheKey(model, key, value, options) {
520
- const modelName = this.getModelName(model);
521
- const optionsHash = JSON.stringify({
522
- key,
523
- include: options.include,
524
- where: options.where,
525
- select: options.select
526
- });
527
- return `${modelName}:${key}:${String(value)}:${optionsHash}`;
528
- }
529
- static getModelName(model) {
530
- if (typeof model === "string") {
531
- return model;
532
- }
533
- if (model && typeof model === "object") {
534
- const obj = model;
535
- if (typeof obj.name === "string") return obj.name;
536
- if (typeof obj.modelName === "string") return obj.modelName;
537
- if (typeof obj.tableName === "string") return obj.tableName;
538
- }
539
- if (model && typeof model === "function") {
540
- return model.name || "Unknown";
541
- }
542
- return "Unknown";
543
- }
544
- static reset() {
545
- this.adapter = null;
546
- this.cache.clear();
547
- this.debug = false;
548
- logger.disable();
549
- }
550
- };
551
- ModelBinder.adapter = null;
552
- ModelBinder.cache = new Cache();
553
- ModelBinder.debug = false;
554
-
555
- // src/core/BaseAdapter.ts
556
- var BaseAdapter = class {
557
- transformValue(_model, _key, value) {
558
- if (/^\d+$/.test(value)) {
559
- const num = parseInt(value, 10);
560
- if (!isNaN(num) && Number.isSafeInteger(num)) {
561
- return num;
562
- }
563
- }
564
- return value;
565
- }
566
- supportsSoftDeletes(_model) {
567
- return false;
568
- }
569
- getModelMetadata(model) {
570
- return {
571
- name: this.getModelName(model),
572
- primaryKey: this.getPrimaryKeyName(model),
573
- softDeletes: this.supportsSoftDeletes(model),
574
- adapter: this.name
575
- };
576
- }
577
- validateModel(model) {
578
- if (!this.isValidModel(model)) {
579
- throw new InvalidModelError(`Invalid model for ${this.name} adapter`, model);
580
- }
581
- }
582
- getModelName(model) {
583
- if (typeof model === "string") {
584
- return model;
585
- }
586
- if (model && typeof model === "object") {
587
- const obj = model;
588
- if (typeof obj.name === "string") return obj.name;
589
- if (typeof obj.modelName === "string") return obj.modelName;
590
- if (typeof obj.tableName === "string") return obj.tableName;
591
- }
592
- if (model && typeof model === "function") {
593
- return model.name || "Unknown";
594
- }
595
- return "Unknown";
596
- }
597
- applySoftDeleteFilter(queryBuilder, _options) {
598
- return queryBuilder;
599
- }
600
- applyIncludes(queryBuilder, _includes) {
601
- return queryBuilder;
602
- }
603
- applySelect(queryBuilder, _select) {
604
- return queryBuilder;
605
- }
606
- applyWhereConditions(queryBuilder, _where) {
607
- return queryBuilder;
608
- }
609
- applyCustomQuery(queryBuilder, queryFn) {
610
- if (queryFn) {
611
- return queryFn(queryBuilder);
612
- }
613
- return queryBuilder;
614
- }
615
- };
616
-
617
- // src/core/types.ts
618
- function isOperatorCondition(value) {
619
- return typeof value === "object" && value !== null && "operator" in value && "value" in value;
620
- }
621
-
622
- // src/utils/validators.ts
623
- var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
624
- var OBJECT_ID_REGEX = /^[0-9a-f]{24}$/i;
625
- function isUUID(value) {
626
- return UUID_REGEX.test(value);
627
- }
628
- function isObjectId(value) {
629
- return OBJECT_ID_REGEX.test(value);
630
- }
631
- function isNumeric(value) {
632
- return /^-?\d+$/.test(value);
633
- }
634
- function isPositiveInteger(value) {
635
- return /^\d+$/.test(value) && parseInt(value, 10) > 0;
636
- }
637
- function isSlug(value) {
638
- return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
639
- }
640
- function isEmail(value) {
641
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
642
- }
643
- function isNotEmpty(value) {
644
- if (value === null || value === void 0) {
645
- return false;
646
- }
647
- if (typeof value === "string") {
648
- return value.trim().length > 0;
649
- }
650
- return true;
651
- }
652
- function isNonEmptyString(value) {
653
- return typeof value === "string" && value.trim().length > 0;
654
- }
655
- function isPlainObject(value) {
656
- return typeof value === "object" && value !== null && !Array.isArray(value);
657
- }
658
- function isFunction(value) {
659
- return typeof value === "function";
660
- }
661
- function isValidParamName(value) {
662
- return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);
663
- }
664
-
665
- // src/adapters/KnexAdapter.ts
666
- function isKnexModel(value) {
667
- return typeof value === "object" && value !== null && typeof value.tableName === "string";
668
- }
669
- var KnexAdapter = class extends BaseAdapter {
670
- constructor(knex) {
671
- super();
672
- this.knex = knex;
673
- this.name = "knex";
674
- }
675
- getKnex() {
676
- return this.knex;
677
- }
678
- async findByKey(model, key, value, options = {}) {
679
- this.validateModel(model);
680
- const tableName = this.getTableName(model);
681
- try {
682
- let query = this.knex(tableName);
683
- query = query.where(key, value);
684
- if (this.supportsSoftDeletes(model) && !options.withTrashed && !options.onlyTrashed) {
685
- const softDeleteColumn = this.getSoftDeleteColumn(model);
686
- query = query.whereNull(softDeleteColumn);
687
- } else if (options.onlyTrashed && this.supportsSoftDeletes(model)) {
688
- const softDeleteColumn = this.getSoftDeleteColumn(model);
689
- query = query.whereNotNull(softDeleteColumn);
690
- }
691
- if (options.where) {
692
- query = this.applyWhereConditions(query, options.where);
693
- }
694
- if (options.select && options.select.length > 0) {
695
- query = query.select(options.select);
696
- } else {
697
- query = query.select("*");
698
- }
699
- if (options.query) {
700
- query = this.applyCustomQuery(
701
- query,
702
- options.query
703
- );
704
- }
705
- if (options.lock === "forUpdate") {
706
- query = query.forUpdate();
707
- } else if (options.lock === "forShare") {
708
- query = query.forShare();
709
- }
710
- const result = await query.first();
711
- return result || null;
712
- } catch (error) {
713
- throw new BindingError(
714
- `Failed to fetch ${this.getModelName(model)}: ${error.message}`,
715
- error
716
- );
717
- }
718
- }
719
- getPrimaryKeyName(model) {
720
- if (isKnexModel(model) && model.primaryKey) {
721
- return model.primaryKey;
722
- }
723
- return "id";
724
- }
725
- isValidModel(model) {
726
- return typeof model === "string" || isKnexModel(model);
727
- }
728
- transformValue(model, key, value) {
729
- const primaryKey = this.getPrimaryKeyName(model);
730
- if (key === primaryKey || key === "id") {
731
- const num = parseInt(value, 10);
732
- if (!isNaN(num) && num.toString() === value && Number.isSafeInteger(num)) {
733
- return num;
734
- }
735
- }
736
- if (isUUID(value)) {
737
- return value;
738
- }
739
- return value;
740
- }
741
- supportsSoftDeletes(model) {
742
- return isKnexModel(model) && !!model.softDeleteColumn;
743
- }
744
- getModelMetadata(model) {
745
- return {
746
- name: this.getModelName(model),
747
- primaryKey: this.getPrimaryKeyName(model),
748
- tableName: this.getTableName(model),
749
- softDeletes: this.supportsSoftDeletes(model),
750
- adapter: this.name
751
- };
752
- }
753
- getTableName(model) {
754
- return typeof model === "string" ? model : model.tableName;
755
- }
756
- getSoftDeleteColumn(model) {
757
- if (isKnexModel(model) && model.softDeleteColumn) {
758
- return model.softDeleteColumn;
759
- }
760
- return "deleted_at";
761
- }
762
- applyWhereConditions(query, where) {
763
- Object.entries(where).forEach(([column, value]) => {
764
- if (value === null) {
765
- query = query.whereNull(column);
766
- } else if (Array.isArray(value)) {
767
- query = query.whereIn(column, value);
768
- } else if (isOperatorCondition(value)) {
769
- query = query.where(column, value.operator, value.value);
770
- } else {
771
- query = query.where(column, value);
772
- }
773
- });
774
- return query;
775
- }
776
- };
777
- function defineKnexModel(config) {
778
- return {
779
- tableName: config.tableName,
780
- primaryKey: config.primaryKey || "id",
781
- softDeleteColumn: config.softDeleteColumn
782
- };
783
- }
784
-
785
- // src/adapters/MongooseAdapter.ts
786
- function isSchemaPathType(value) {
787
- return typeof value === "object" && value !== null && "instance" in value;
788
- }
789
- var MongooseAdapter = class extends BaseAdapter {
790
- constructor() {
791
- super(...arguments);
792
- this.name = "mongoose";
793
- }
794
- async findByKey(model, key, value, options = {}) {
795
- this.validateModel(model);
796
- try {
797
- const transformedValue = this.transformValue(model, key, value);
798
- let query = model.findOne({ [key]: transformedValue });
799
- if (options.select && options.select.length > 0) {
800
- query = query.select(options.select.join(" "));
801
- }
802
- if (options.include) {
803
- query = this.applyIncludes(query, options.include);
804
- }
805
- if (options.where) {
806
- Object.entries(options.where).forEach(([field, val]) => {
807
- query = query.where(field).equals(val);
808
- });
809
- }
810
- if (options.query) {
811
- query = this.applyCustomQuery(query, options.query);
812
- }
813
- const result = await query.exec();
814
- if (result && this.supportsSoftDeletes(model)) {
815
- const doc = result;
816
- const isDeleted = doc.deleted || doc.deletedAt || doc.isDeleted;
817
- if (!options.withTrashed && !options.onlyTrashed && isDeleted) {
818
- return null;
819
- }
820
- if (options.onlyTrashed && !isDeleted) {
821
- return null;
822
- }
823
- }
824
- return result;
825
- } catch (error) {
826
- throw new BindingError(
827
- `Failed to fetch ${model.modelName}: ${error.message}`,
828
- error
829
- );
830
- }
831
- }
832
- getPrimaryKeyName(_model) {
833
- return "_id";
834
- }
835
- isValidModel(model) {
836
- if (!model || typeof model !== "function") {
837
- return false;
838
- }
839
- const m = model;
840
- return typeof m.findOne === "function" && typeof m.modelName === "string" && m.schema !== void 0;
841
- }
842
- transformValue(model, key, value) {
843
- if (key === "_id" || key === "id") {
844
- if (isObjectId(value)) {
845
- try {
846
- const mongoose = require("mongoose");
847
- return new mongoose.Types.ObjectId(value);
848
- } catch {
849
- return value;
850
- }
851
- }
852
- return value;
853
- }
854
- const schemaType = model.schema.path(key);
855
- if (isSchemaPathType(schemaType) && schemaType.instance === "Number") {
856
- const num = parseInt(value, 10);
857
- if (!isNaN(num)) {
858
- return num;
859
- }
860
- }
861
- return value;
862
- }
863
- supportsSoftDeletes(model) {
864
- const schema = model.schema;
865
- return schema.path("deleted") !== void 0 || schema.path("deletedAt") !== void 0 || schema.path("isDeleted") !== void 0;
866
- }
867
- getModelMetadata(model) {
868
- const schema = model.schema;
869
- const paths = Object.keys(schema.paths);
870
- return {
871
- name: model.modelName,
872
- primaryKey: "_id",
873
- tableName: model.collection.name,
874
- softDeletes: this.supportsSoftDeletes(model),
875
- fields: paths,
876
- adapter: this.name
877
- };
878
- }
879
- applyIncludes(query, includes) {
880
- if (Array.isArray(includes)) {
881
- includes.forEach((relation) => {
882
- query = query.populate(relation);
883
- });
884
- } else {
885
- Object.entries(includes).forEach(([relation, options]) => {
886
- if (typeof options === "boolean" && options) {
887
- query = query.populate(relation);
888
- } else if (typeof options === "object" && options !== null) {
889
- query = query.populate({
890
- path: relation,
891
- ...options
892
- });
893
- }
894
- });
895
- }
896
- return query;
897
- }
898
- };
899
-
900
- // src/adapters/TypeORMAdapter.ts
901
- var TypeORMAdapter = class extends BaseAdapter {
902
- constructor(dataSource) {
903
- super();
904
- this.dataSource = dataSource;
905
- this.name = "typeorm";
906
- }
907
- getDataSource() {
908
- return this.dataSource;
909
- }
910
- async findByKey(entity, key, value, options = {}) {
911
- this.validateModel(entity);
912
- try {
913
- const repository = this.dataSource.getRepository(entity);
914
- const metadata = repository.metadata;
915
- const transformedValue = this.transformValue(entity, key, value);
916
- if (options.query || options.lock || options.onlyTrashed) {
917
- return await this.findWithQueryBuilder(repository, key, transformedValue, options);
918
- }
919
- const findOptions = {
920
- where: { [key]: transformedValue }
921
- };
922
- if (options.select && options.select.length > 0) {
923
- findOptions.select = options.select.reduce((acc, field) => {
924
- acc[field] = true;
925
- return acc;
926
- }, {});
927
- }
928
- if (options.include) {
929
- findOptions.relations = Array.isArray(options.include) ? options.include : Object.keys(options.include);
930
- }
931
- if (options.where) {
932
- findOptions.where = {
933
- ...findOptions.where,
934
- ...options.where
935
- };
936
- }
937
- if (options.withTrashed && metadata.deleteDateColumn) {
938
- findOptions.withDeleted = true;
939
- }
940
- const result = await repository.findOne(findOptions);
941
- return result;
942
- } catch (error) {
943
- throw new BindingError(`Failed to fetch entity: ${error.message}`, error);
944
- }
945
- }
946
- getPrimaryKeyName(entity) {
947
- try {
948
- const repository = this.dataSource.getRepository(entity);
949
- const metadata = repository.metadata;
950
- if (metadata.primaryColumns.length > 0) {
951
- return metadata.primaryColumns[0].propertyName;
952
- }
953
- } catch {
954
- }
955
- return "id";
956
- }
957
- isValidModel(model) {
958
- try {
959
- this.dataSource.getRepository(model);
960
- return true;
961
- } catch {
962
- return false;
963
- }
964
- }
965
- transformValue(entity, key, value) {
966
- try {
967
- const repository = this.dataSource.getRepository(entity);
968
- const metadata = repository.metadata;
969
- const column = metadata.findColumnWithPropertyName(key);
970
- if (!column) {
971
- if (isUUID(value)) {
972
- return value;
973
- }
974
- const num = parseInt(value, 10);
975
- if (!isNaN(num) && num.toString() === value) {
976
- return num;
977
- }
978
- return value;
979
- }
980
- const columnType = String(column.type).toLowerCase();
981
- if (["int", "integer", "smallint", "bigint", "number"].includes(columnType)) {
982
- const num = parseInt(value, 10);
983
- return isNaN(num) ? value : num;
984
- }
985
- if (columnType === "uuid") {
986
- return value;
987
- }
988
- if (columnType === "boolean" || columnType === "bool") {
989
- return value === "true" || value === "1";
990
- }
991
- return value;
992
- } catch {
993
- return value;
994
- }
995
- }
996
- supportsSoftDeletes(entity) {
997
- try {
998
- const repository = this.dataSource.getRepository(entity);
999
- const metadata = repository.metadata;
1000
- return !!metadata.deleteDateColumn;
1001
- } catch {
1002
- return false;
1003
- }
1004
- }
1005
- getModelMetadata(entity) {
1006
- try {
1007
- const repository = this.dataSource.getRepository(entity);
1008
- const metadata = repository.metadata;
1009
- return {
1010
- name: metadata.name,
1011
- primaryKey: this.getPrimaryKeyName(entity),
1012
- tableName: metadata.tableName,
1013
- softDeletes: this.supportsSoftDeletes(entity),
1014
- relations: metadata.relations.map((r) => r.propertyName),
1015
- adapter: this.name
1016
- };
1017
- } catch {
1018
- return {
1019
- name: "Unknown",
1020
- primaryKey: "id",
1021
- softDeletes: false,
1022
- adapter: this.name
1023
- };
1024
- }
1025
- }
1026
- async findWithQueryBuilder(repository, key, value, options) {
1027
- const metadata = repository.metadata;
1028
- const alias = metadata.name.toLowerCase();
1029
- let queryBuilder = repository.createQueryBuilder(alias).where(`${alias}.${key} = :value`, { value });
1030
- if (options.include) {
1031
- const relations = Array.isArray(options.include) ? options.include : Object.keys(options.include);
1032
- relations.forEach((relation) => {
1033
- queryBuilder = queryBuilder.leftJoinAndSelect(`${alias}.${relation}`, relation);
1034
- });
1035
- }
1036
- if (options.where) {
1037
- Object.entries(options.where).forEach(([field, val]) => {
1038
- queryBuilder = queryBuilder.andWhere(`${alias}.${field} = :${field}`, { [field]: val });
1039
- });
1040
- }
1041
- if (options.select && options.select.length > 0) {
1042
- queryBuilder = queryBuilder.select(options.select.map((field) => `${alias}.${field}`));
1043
- }
1044
- if (options.withTrashed) {
1045
- queryBuilder = queryBuilder.withDeleted();
1046
- } else if (options.onlyTrashed && metadata.deleteDateColumn) {
1047
- queryBuilder = queryBuilder.withDeleted().andWhere(`${alias}.${metadata.deleteDateColumn.propertyName} IS NOT NULL`);
1048
- }
1049
- if (options.lock === "forUpdate") {
1050
- queryBuilder = queryBuilder.setLock("pessimistic_write");
1051
- } else if (options.lock === "forShare") {
1052
- queryBuilder = queryBuilder.setLock("pessimistic_read");
1053
- }
1054
- if (options.query) {
1055
- queryBuilder = this.applyCustomQuery(
1056
- queryBuilder,
1057
- options.query
1058
- );
1059
- }
1060
- return await queryBuilder.getOne();
1061
- }
1062
- };
1063
-
1064
- // src/adapters/SequelizeAdapter.ts
1065
- function isSequelizeModel(model) {
1066
- if (!model || typeof model !== "function") {
1067
- return false;
1068
- }
1069
- const m = model;
1070
- return typeof m.findOne === "function" && typeof m.findAll === "function" && typeof m.rawAttributes === "object";
1071
- }
1072
- var SequelizeAdapter = class extends BaseAdapter {
1073
- constructor(sequelize) {
1074
- super();
1075
- this.sequelize = sequelize;
1076
- this.name = "sequelize";
1077
- }
1078
- getSequelize() {
1079
- return this.sequelize;
1080
- }
1081
- async findByKey(model, key, value, options = {}) {
1082
- this.validateModel(model);
1083
- try {
1084
- const transformedValue = this.transformValue(model, key, value);
1085
- const findOptions = {
1086
- where: { [key]: transformedValue }
1087
- };
1088
- if (options.select && options.select.length > 0) {
1089
- findOptions.attributes = options.select;
1090
- }
1091
- if (options.include) {
1092
- findOptions.include = this.buildIncludes(options.include);
1093
- }
1094
- if (options.where) {
1095
- findOptions.where = {
1096
- ...findOptions.where,
1097
- ...options.where
1098
- };
1099
- }
1100
- const isParanoid = model.options?.paranoid;
1101
- if (isParanoid) {
1102
- if (options.withTrashed) {
1103
- findOptions.paranoid = false;
1104
- } else if (options.onlyTrashed) {
1105
- findOptions.paranoid = false;
1106
- const { Op: SeqOp } = require("sequelize");
1107
- const whereClause = findOptions.where;
1108
- whereClause.deletedAt = { [SeqOp.ne]: null };
1109
- }
1110
- }
1111
- if (options.lock === "forUpdate") {
1112
- findOptions.lock = true;
1113
- } else if (options.lock === "forShare") {
1114
- const { Transaction: SeqTransaction } = require("sequelize");
1115
- findOptions.lock = SeqTransaction.LOCK.SHARE;
1116
- }
1117
- let result = await model.findOne(findOptions);
1118
- if (options.query && !result) {
1119
- result = await this.findWithCustomQuery(model, key, transformedValue, options);
1120
- }
1121
- return result;
1122
- } catch (error) {
1123
- throw new BindingError(
1124
- `Failed to fetch ${model.name}: ${error.message}`,
1125
- error
1126
- );
1127
- }
1128
- }
1129
- getPrimaryKeyName(model) {
1130
- return model.primaryKeyAttribute || "id";
1131
- }
1132
- isValidModel(model) {
1133
- return isSequelizeModel(model);
1134
- }
1135
- transformValue(model, key, value) {
1136
- const attributes = model.rawAttributes;
1137
- const attribute = attributes?.[key];
1138
- if (!attribute) {
1139
- if (isUUID(value)) {
1140
- return value;
1141
- }
1142
- const num = parseInt(value, 10);
1143
- if (!isNaN(num) && num.toString() === value) {
1144
- return num;
1145
- }
1146
- return value;
1147
- }
1148
- const attrType = attribute.type;
1149
- const type = attrType?.constructor?.name || String(attrType);
1150
- switch (type) {
1151
- case "INTEGER":
1152
- case "BIGINT":
1153
- case "SMALLINT": {
1154
- const intNum = parseInt(value, 10);
1155
- return isNaN(intNum) ? value : intNum;
1156
- }
1157
- case "FLOAT":
1158
- case "DOUBLE":
1159
- case "DECIMAL": {
1160
- const floatNum = parseFloat(value);
1161
- return isNaN(floatNum) ? value : floatNum;
1162
- }
1163
- case "BOOLEAN":
1164
- return value === "true" || value === "1";
1165
- case "UUID":
1166
- return value;
1167
- default:
1168
- return value;
1169
- }
1170
- }
1171
- supportsSoftDeletes(model) {
1172
- return !!model.options?.paranoid;
1173
- }
1174
- getModelMetadata(model) {
1175
- const tableName = model.tableName || model.name;
1176
- const associations = Object.keys(model.associations || {});
1177
- return {
1178
- name: model.name,
1179
- primaryKey: this.getPrimaryKeyName(model),
1180
- tableName,
1181
- softDeletes: this.supportsSoftDeletes(model),
1182
- relations: associations,
1183
- adapter: this.name
1184
- };
1185
- }
1186
- buildIncludes(includes) {
1187
- if (Array.isArray(includes)) {
1188
- return includes.map((relation) => ({ association: relation }));
1189
- }
1190
- return Object.entries(includes).map(([relation, opts]) => {
1191
- if (typeof opts === "boolean" && opts) {
1192
- return { association: relation };
1193
- }
1194
- if (typeof opts === "object" && opts !== null) {
1195
- return {
1196
- association: relation,
1197
- ...opts
1198
- };
1199
- }
1200
- return { association: relation };
1201
- });
1202
- }
1203
- async findWithCustomQuery(model, key, value, options) {
1204
- const findOptions = {
1205
- where: { [key]: value }
1206
- };
1207
- if (options.query) {
1208
- options.query(findOptions);
1209
- }
1210
- return await model.findOne(findOptions);
1211
- }
1212
- };
1213
-
1214
- // src/adapters/PrismaAdapter.ts
1215
- var PrismaAdapter = class extends BaseAdapter {
1216
- constructor(prisma) {
1217
- super();
1218
- this.prisma = prisma;
1219
- this.name = "prisma";
1220
- }
1221
- getPrisma() {
1222
- return this.prisma;
1223
- }
1224
- async findByKey(modelName, key, value, options = {}) {
1225
- this.validateModel(modelName);
1226
- try {
1227
- const model = this.prisma[modelName];
1228
- if (!model) {
1229
- throw new InvalidModelError(`Model '${modelName}' not found in Prisma schema`, modelName);
1230
- }
1231
- const transformedValue = this.transformValue(modelName, key, value);
1232
- const queryOptions = {
1233
- where: { [key]: transformedValue }
1234
- };
1235
- if (options.where) {
1236
- queryOptions.where = {
1237
- ...queryOptions.where,
1238
- ...options.where
1239
- };
1240
- }
1241
- if (options.select && options.select.length > 0) {
1242
- queryOptions.select = options.select.reduce((acc, field) => {
1243
- acc[field] = true;
1244
- return acc;
1245
- }, {});
1246
- }
1247
- if (options.include) {
1248
- queryOptions.include = this.buildIncludeOptions(options.include);
1249
- delete queryOptions.select;
1250
- }
1251
- if (options.query) {
1252
- options.query(queryOptions);
1253
- }
1254
- let result = null;
1255
- try {
1256
- result = await model.findUnique(queryOptions);
1257
- } catch {
1258
- result = await model.findFirst(queryOptions);
1259
- }
1260
- if (!result && key !== this.getPrimaryKeyName(modelName)) {
1261
- result = await model.findFirst(queryOptions);
1262
- }
1263
- return result;
1264
- } catch (error) {
1265
- if (error instanceof InvalidModelError) {
1266
- throw error;
1267
- }
1268
- throw new BindingError(
1269
- `Failed to fetch ${modelName}: ${error.message}`,
1270
- error
1271
- );
1272
- }
1273
- }
1274
- getPrimaryKeyName(_modelName) {
1275
- return "id";
1276
- }
1277
- isValidModel(modelName) {
1278
- if (typeof modelName !== "string") {
1279
- return false;
1280
- }
1281
- if (modelName.startsWith("$")) {
1282
- return false;
1283
- }
1284
- return !!this.prisma[modelName];
1285
- }
1286
- transformValue(_modelName, key, value) {
1287
- if (key === "id" || key.endsWith("Id")) {
1288
- const num = parseInt(value, 10);
1289
- if (!isNaN(num) && num.toString() === value && Number.isSafeInteger(num)) {
1290
- return num;
1291
- }
1292
- }
1293
- if (isUUID(value)) {
1294
- return value;
1295
- }
1296
- return value;
1297
- }
1298
- supportsSoftDeletes(_modelName) {
1299
- return false;
1300
- }
1301
- getModelMetadata(modelName) {
1302
- return {
1303
- name: modelName,
1304
- primaryKey: this.getPrimaryKeyName(modelName),
1305
- softDeletes: false,
1306
- adapter: this.name
1307
- };
1308
- }
1309
- buildIncludeOptions(includes) {
1310
- if (Array.isArray(includes)) {
1311
- return includes.reduce((acc, relation) => {
1312
- acc[relation] = true;
1313
- return acc;
1314
- }, {});
1315
- }
1316
- return Object.entries(includes).reduce((acc, [relation, opts]) => {
1317
- if (typeof opts === "boolean") {
1318
- acc[relation] = opts;
1319
- } else if (typeof opts === "object" && opts !== null) {
1320
- const nestedOpts = opts;
1321
- acc[relation] = {
1322
- ...nestedOpts,
1323
- include: nestedOpts.include ? this.buildIncludeOptions(nestedOpts.include) : void 0
1324
- };
1325
- }
1326
- return acc;
1327
- }, {});
1328
- }
1329
- };
1330
-
1331
- // src/middleware/bindModel.ts
1332
- function bindModel(paramName, model, options = {}) {
1333
- return async (req, res, next) => {
1334
- try {
1335
- if (!(paramName in req.params)) {
1336
- logger.warn(`Parameter '${paramName}' not found in route`);
1337
- if (options.optional) {
1338
- return next();
1339
- }
1340
- }
1341
- const result = await ModelBinder.bind(req, res, paramName, model, options);
1342
- if (!result.success) {
1343
- return next(result.error);
1344
- }
1345
- next();
1346
- } catch (error) {
1347
- next(error);
1348
- }
1349
- };
1350
- }
1351
- function bindModels(bindings) {
1352
- return async (req, res, next) => {
1353
- try {
1354
- for (const [paramName, config] of Object.entries(bindings)) {
1355
- if (!(paramName in req.params)) {
1356
- logger.warn(`Parameter '${paramName}' not found in route`);
1357
- if (config.options?.optional) {
1358
- continue;
1359
- }
1360
- }
1361
- const result = await ModelBinder.bind(
1362
- req,
1363
- res,
1364
- paramName,
1365
- config.model,
1366
- config.options || {}
1367
- );
1368
- if (!result.success) {
1369
- return next(result.error);
1370
- }
1371
- }
1372
- next();
1373
- } catch (error) {
1374
- next(error);
1375
- }
1376
- };
1377
- }
1378
- function bindOptional(paramName, model, options = {}) {
1379
- return bindModel(paramName, model, { ...options, optional: true });
1380
- }
1381
- function bindByKey(paramName, model, key, options = {}) {
1382
- return bindModel(paramName, model, { ...options, key });
1383
- }
1384
- function bindAs(paramName, model, attachAs, options = {}) {
1385
- return bindModel(paramName, model, { ...options, as: attachAs });
1386
- }
1387
- function bindCached(paramName, model, ttl = 6e4, options = {}) {
1388
- return bindModel(paramName, model, { ...options, cache: true, cacheTTL: ttl });
1389
- }
1390
- function bindWithRelations(paramName, model, relations, options = {}) {
1391
- return bindModel(paramName, model, { ...options, include: relations });
1392
- }
1393
-
1394
- // src/utils/transformers.ts
1395
- function toNumber(value) {
1396
- if (isNumeric(value)) {
1397
- const num = parseInt(value, 10);
1398
- if (!isNaN(num) && Number.isSafeInteger(num)) {
1399
- return num;
1400
- }
1401
- }
1402
- return value;
1403
- }
1404
- function toFloat(value) {
1405
- const num = parseFloat(value);
1406
- if (!isNaN(num) && isFinite(num)) {
1407
- return num;
1408
- }
1409
- return value;
1410
- }
1411
- function toBoolean(value) {
1412
- const lowered = value.toLowerCase();
1413
- return lowered === "true" || lowered === "1" || lowered === "yes";
1414
- }
1415
- function toLowerCase(value) {
1416
- return value.toLowerCase();
1417
- }
1418
- function toUpperCase(value) {
1419
- return value.toUpperCase();
1420
- }
1421
- function trim(value) {
1422
- return value.trim();
1423
- }
1424
- function slugToUnderscore(value) {
1425
- return value.replace(/-/g, "_");
1426
- }
1427
- function underscoreToSlug(value) {
1428
- return value.replace(/_/g, "-");
1429
- }
1430
- function autoTransform(value) {
1431
- if (isUUID(value)) {
1432
- return value;
1433
- }
1434
- if (isObjectId(value)) {
1435
- return value;
1436
- }
1437
- if (/^\d+$/.test(value)) {
1438
- const num = parseInt(value, 10);
1439
- if (!isNaN(num) && Number.isSafeInteger(num)) {
1440
- return num;
1441
- }
1442
- }
1443
- if (/^-\d+$/.test(value)) {
1444
- const num = parseInt(value, 10);
1445
- if (!isNaN(num) && Number.isSafeInteger(num)) {
1446
- return num;
1447
- }
1448
- }
1449
- return value;
1450
- }
1451
- function compose(...transformers) {
1452
- return (value) => {
1453
- let result = value;
1454
- for (const transformer of transformers) {
1455
- result = transformer(String(result));
1456
- }
1457
- return result;
1458
- };
1459
- }
1460
- function identity(value) {
1461
- return value;
1462
- }
1463
-
1464
- // src/index.ts
1465
- var VERSION = "1.0.0";
1466
- // Annotate the CommonJS export names for ESM import in node:
1467
- 0 && (module.exports = {
1468
- AdapterNotSetError,
1469
- BaseAdapter,
1470
- BindingError,
1471
- Cache,
1472
- InvalidModelError,
1473
- KnexAdapter,
1474
- ModelBinder,
1475
- ModelNotFoundError,
1476
- MongooseAdapter,
1477
- PrismaAdapter,
1478
- SequelizeAdapter,
1479
- TypeORMAdapter,
1480
- VERSION,
1481
- ValidationError,
1482
- autoTransform,
1483
- bindAs,
1484
- bindByKey,
1485
- bindCached,
1486
- bindModel,
1487
- bindModels,
1488
- bindOptional,
1489
- bindWithRelations,
1490
- compose,
1491
- defineKnexModel,
1492
- identity,
1493
- isEmail,
1494
- isFunction,
1495
- isNonEmptyString,
1496
- isNotEmpty,
1497
- isNumeric,
1498
- isObjectId,
1499
- isPlainObject,
1500
- isPositiveInteger,
1501
- isSlug,
1502
- isUUID,
1503
- isValidParamName,
1504
- logger,
1505
- slugToUnderscore,
1506
- toBoolean,
1507
- toFloat,
1508
- toLowerCase,
1509
- toNumber,
1510
- toUpperCase,
1511
- trim,
1512
- underscoreToSlug
1513
- });
1514
- //# sourceMappingURL=index.js.map
1
+ 'use strict';var chunkEEPSARTF_js=require('./chunk-EEPSARTF.js'),chunkCKT6LD2K_js=require('./chunk-CKT6LD2K.js'),chunkZJ2WW53U_js=require('./chunk-ZJ2WW53U.js'),chunkISSVD3WP_js=require('./chunk-ISSVD3WP.js'),chunkOKPAWWZN_js=require('./chunk-OKPAWWZN.js'),chunk7I3EYD6K_js=require('./chunk-7I3EYD6K.js');var y=class{constructor(e=1e3){this.store=new Map;this.maxSize=e;}get(e){let r=this.store.get(e);return r?Date.now()-r.timestamp>r.ttl?(this.store.delete(e),null):r.value:null}set(e,r,n){if(this.store.size>=this.maxSize){let o=this.store.keys().next().value;o&&this.store.delete(o);}this.store.set(e,{value:r,timestamp:Date.now(),ttl:n});}delete(e){return this.store.delete(e)}clear(){this.store.clear();}get size(){return this.store.size}has(e){return this.get(e)!==null}keys(){return Array.from(this.store.keys())}prune(){let e=0,r=Date.now();for(let[n,o]of this.store.entries())r-o.timestamp>o.ttl&&(this.store.delete(n),e++);return e}getStats(){return {size:this.store.size,maxSize:this.maxSize}}};var M=class{constructor(){this.config={enabled:false,level:"info",prefix:"[express-model-binding]"};this.levelPriority={debug:0,info:1,warn:2,error:3};}enable(){this.config.enabled=true;}disable(){this.config.enabled=false;}isEnabled(){return this.config.enabled}setLevel(e){this.config.level=e;}getLevel(){return this.config.level}setPrefix(e){this.config.prefix=e;}shouldLog(e){return this.config.enabled?this.levelPriority[e]>=this.levelPriority[this.config.level]:false}formatMessage(e,r){let n=new Date().toISOString();return `${this.config.prefix} [${n}] [${e.toUpperCase()}] ${r}`}debug(e,r){this.shouldLog("debug")&&console.log(this.formatMessage("debug",e),r!==void 0?r:"");}info(e,r){this.shouldLog("info")&&console.info(this.formatMessage("info",e),r!==void 0?r:"");}warn(e,r){this.shouldLog("warn")&&console.warn(this.formatMessage("warn",e),r!==void 0?r:"");}error(e,r){this.shouldLog("error")&&console.error(this.formatMessage("error",e),r!==void 0?r:"");}reset(){this.config={enabled:false,level:"info",prefix:"[express-model-binding]"};}getConfig(){return {...this.config}}},s=new M;var l=class{static setAdapter(e){if(!e)throw new Error("Adapter cannot be null or undefined");this.adapter=e,s.debug(`Adapter set: ${e.name}`);}static getAdapter(){if(!this.adapter)throw new chunk7I3EYD6K_js.d("No adapter set. Call ModelBinder.setAdapter() before using model binding.");return this.adapter}static hasAdapter(){return this.adapter!==null}static clearAdapter(){this.adapter=null,s.debug("Adapter cleared");}static setDebug(e){this.debug=e,e?s.enable():s.disable();}static isDebugEnabled(){return this.debug}static clearCache(){this.cache.clear(),s.debug("Cache cleared");}static getCacheStats(){return this.cache.getStats()}static async bind(e,r,n,o,i={}){let a=Date.now(),u=this.getAdapter(),f=e.params[n],A={req:e,res:r,paramName:n,paramValue:f,model:o,options:i,adapter:u,startTime:a};try{if(f==null||f===""){if(i.optional){let d=i.as||n;return this.attachToRequest(e,d,void 0),{success:!0,model:void 0,duration:Date.now()-a}}throw new chunk7I3EYD6K_js.b(`Parameter '${n}' is required but was not provided`,new Error("Missing parameter"))}if(!u.isValidModel(o))throw new chunk7I3EYD6K_js.b(`Invalid model for ${u.name} adapter`,new Error("Model validation failed"));let p=i.key||u.getPrimaryKeyName(o),c=A.paramValue;i.transformValue?c=i.transformValue(f):c=u.transformValue(o,p,f);let C=this.getCacheKey(o,p,c,i);if(i.cache){let d=this.cache.get(C);if(d!==null){s.debug(`Cache hit for ${n}:${c}`);let v=i.as||n;return this.attachToRequest(e,v,d),{success:!0,model:d,duration:Date.now()-a,fromCache:!0}}}s.debug(`Fetching ${n}:${c} using ${u.name}`);let b=await u.findByKey(o,p,c,i);if(!b){if(i.optional){let v=i.as||n;return this.attachToRequest(e,v,void 0),{success:!0,model:void 0,duration:Date.now()-a}}let d;throw i.errorMessage?d=new chunk7I3EYD6K_js.c(n,String(c),this.getModelName(o),i.errorMessage):i.onNotFound?d=typeof i.onNotFound=="function"?i.onNotFound(n,String(c)):i.onNotFound:d=new chunk7I3EYD6K_js.c(n,String(c),this.getModelName(o)),d}if(i.validate&&await i.validate(b,e),i.cache){let d=i.cacheTTL||(typeof i.cache=="number"?i.cache:6e4);this.cache.set(C,b,d),s.debug(`Cached ${n}:${c} for ${d}ms`);}let O=i.as||n;return this.attachToRequest(e,O,b),s.debug(`Successfully bound ${n}:${c}`),{success:!0,model:b,duration:Date.now()-a,fromCache:!1}}catch(p){return s.error(`Failed to bind ${n}:${A.paramValue}`,p),{success:false,error:p,duration:Date.now()-a}}}static attachToRequest(e,r,n){if(!chunk7I3EYD6K_js.q(r))throw new chunk7I3EYD6K_js.b(`Invalid attachment key '${r}': reserved or invalid property name`,new Error("Security violation: attempted prototype pollution"));e[r]=n;}static getCacheKey(e,r,n,o){let i=this.getModelName(e),a=String(r).slice(0,64),u=String(n).slice(0,256),f=JSON.stringify({key:a,include:Array.isArray(o.include)?o.include.slice(0,10):void 0,select:Array.isArray(o.select)?o.select.slice(0,20):void 0});return `${i}:${a}:${u}:${f}`}static getModelName(e){if(typeof e=="string")return e;if(e&&typeof e=="object"){let r=e;if(typeof r.name=="string")return r.name;if(typeof r.modelName=="string")return r.modelName;if(typeof r.tableName=="string")return r.tableName}return e&&typeof e=="function"&&e.name||"Unknown"}static reset(){this.adapter=null,this.cache.clear(),this.debug=false,s.disable();}};l.adapter=null,l.cache=new y,l.debug=false;var S=1024;function B(t){return t?t.length>S?t.slice(0,S):t:""}function h(t,e,r={}){if(!chunk7I3EYD6K_js.q(t))throw new chunk7I3EYD6K_js.b(`Invalid parameter name '${t}': must be alphanumeric with underscores`,new Error("Invalid parameter name"));return async(n,o,i)=>{try{if(!(t in n.params)&&(s.warn(`Parameter '${t}' not found in route`),r.optional))return i();let a=n.params[t];n.params[t]=B(a);let u=await l.bind(n,o,t,e,r);if(!u.success)return i(u.error);i();}catch(a){i(a);}}}function H(t){for(let e of Object.keys(t))if(!chunk7I3EYD6K_js.q(e))throw new chunk7I3EYD6K_js.b(`Invalid parameter name '${e}': must be alphanumeric with underscores`,new Error("Invalid parameter name"));return async(e,r,n)=>{try{for(let[o,i]of Object.entries(t)){if(!(o in e.params)&&(s.warn(`Parameter '${o}' not found in route`),i.options?.optional))continue;let a=e.params[o];e.params[o]=B(a);let u=await l.bind(e,r,o,i.model,i.options||{});if(!u.success)return n(u.error)}n();}catch(o){n(o);}}}function U(t,e,r={}){return h(t,e,{...r,optional:true})}function _(t,e,r,n={}){return h(t,e,{...n,key:r})}function j(t,e,r,n={}){return h(t,e,{...n,as:r})}function W(t,e,r=6e4,n={}){return h(t,e,{...n,cache:true,cacheTTL:r})}function G(t,e,r,n={}){return h(t,e,{...n,include:r})}var m=1024;function he(t){if(t.length>20)return t;if(chunk7I3EYD6K_js.i(t)){let e=parseInt(t,10);if(!isNaN(e)&&Number.isSafeInteger(e))return e}return t}function me(t){if(t.length>30)return t;let e=parseFloat(t);return !isNaN(e)&&isFinite(e)?e:t}function be(t){let e=t.toLowerCase();return e==="true"||e==="1"||e==="yes"}function we(t){return t.length>m?t.slice(0,m).toLowerCase():t.toLowerCase()}function ye(t){return t.length>m?t.slice(0,m).toUpperCase():t.toUpperCase()}function xe(t){return t.length>m*2?t.slice(0,m*2).trim():t.trim()}function ve(t){return t.replace(/-/g,"_")}function Re(t){return t.replace(/_/g,"-")}function Me(t){if(chunk7I3EYD6K_js.g(t)||chunk7I3EYD6K_js.h(t))return t;if(/^\d+$/.test(t)){let e=parseInt(t,10);if(!isNaN(e)&&Number.isSafeInteger(e))return e}if(/^-\d+$/.test(t)){let e=parseInt(t,10);if(!isNaN(e)&&Number.isSafeInteger(e))return e}return t}function Ae(...t){return e=>{let r=e;for(let n of t)r=n(String(r));return r}}function Ce(t){return t}var Le="1.1.0";Object.defineProperty(exports,"KnexAdapter",{enumerable:true,get:function(){return chunkEEPSARTF_js.a}});Object.defineProperty(exports,"defineKnexModel",{enumerable:true,get:function(){return chunkEEPSARTF_js.b}});Object.defineProperty(exports,"MongooseAdapter",{enumerable:true,get:function(){return chunkCKT6LD2K_js.a}});Object.defineProperty(exports,"TypeORMAdapter",{enumerable:true,get:function(){return chunkZJ2WW53U_js.a}});Object.defineProperty(exports,"SequelizeAdapter",{enumerable:true,get:function(){return chunkISSVD3WP_js.a}});Object.defineProperty(exports,"PrismaAdapter",{enumerable:true,get:function(){return chunkOKPAWWZN_js.a}});Object.defineProperty(exports,"AdapterNotSetError",{enumerable:true,get:function(){return chunk7I3EYD6K_js.d}});Object.defineProperty(exports,"BaseAdapter",{enumerable:true,get:function(){return chunk7I3EYD6K_js.r}});Object.defineProperty(exports,"BindingError",{enumerable:true,get:function(){return chunk7I3EYD6K_js.b}});Object.defineProperty(exports,"InvalidModelError",{enumerable:true,get:function(){return chunk7I3EYD6K_js.e}});Object.defineProperty(exports,"ModelNotFoundError",{enumerable:true,get:function(){return chunk7I3EYD6K_js.c}});Object.defineProperty(exports,"ValidationError",{enumerable:true,get:function(){return chunk7I3EYD6K_js.f}});Object.defineProperty(exports,"isEmail",{enumerable:true,get:function(){return chunk7I3EYD6K_js.l}});Object.defineProperty(exports,"isFunction",{enumerable:true,get:function(){return chunk7I3EYD6K_js.p}});Object.defineProperty(exports,"isNonEmptyString",{enumerable:true,get:function(){return chunk7I3EYD6K_js.n}});Object.defineProperty(exports,"isNotEmpty",{enumerable:true,get:function(){return chunk7I3EYD6K_js.m}});Object.defineProperty(exports,"isNumeric",{enumerable:true,get:function(){return chunk7I3EYD6K_js.i}});Object.defineProperty(exports,"isObjectId",{enumerable:true,get:function(){return chunk7I3EYD6K_js.h}});Object.defineProperty(exports,"isOperatorCondition",{enumerable:true,get:function(){return chunk7I3EYD6K_js.t}});Object.defineProperty(exports,"isPlainObject",{enumerable:true,get:function(){return chunk7I3EYD6K_js.o}});Object.defineProperty(exports,"isPositiveInteger",{enumerable:true,get:function(){return chunk7I3EYD6K_js.j}});Object.defineProperty(exports,"isSlug",{enumerable:true,get:function(){return chunk7I3EYD6K_js.k}});Object.defineProperty(exports,"isUUID",{enumerable:true,get:function(){return chunk7I3EYD6K_js.g}});Object.defineProperty(exports,"isValidFieldName",{enumerable:true,get:function(){return chunk7I3EYD6K_js.s}});Object.defineProperty(exports,"isValidParamName",{enumerable:true,get:function(){return chunk7I3EYD6K_js.q}});exports.Cache=y;exports.ModelBinder=l;exports.VERSION=Le;exports.autoTransform=Me;exports.bindAs=j;exports.bindByKey=_;exports.bindCached=W;exports.bindModel=h;exports.bindModels=H;exports.bindOptional=U;exports.bindWithRelations=G;exports.compose=Ae;exports.identity=Ce;exports.logger=s;exports.slugToUnderscore=ve;exports.toBoolean=be;exports.toFloat=me;exports.toLowerCase=we;exports.toNumber=he;exports.toUpperCase=ye;exports.trim=xe;exports.underscoreToSlug=Re;