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