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.
- package/dist/{BaseAdapter-BjvLQijd.d.mts → BaseAdapter-Dr9a8ZUY.d.mts} +17 -1
- package/dist/{BaseAdapter-BjvLQijd.d.ts → BaseAdapter-Dr9a8ZUY.d.ts} +17 -1
- package/dist/adapters/KnexAdapter.d.mts +1 -1
- package/dist/adapters/KnexAdapter.d.ts +1 -1
- package/dist/adapters/KnexAdapter.js +1 -257
- package/dist/adapters/KnexAdapter.mjs +1 -229
- package/dist/adapters/MongooseAdapter.d.mts +1 -1
- package/dist/adapters/MongooseAdapter.d.ts +1 -1
- package/dist/adapters/MongooseAdapter.js +1 -245
- package/dist/adapters/MongooseAdapter.mjs +1 -225
- package/dist/adapters/PrismaAdapter.d.mts +1 -1
- package/dist/adapters/PrismaAdapter.d.ts +1 -1
- package/dist/adapters/PrismaAdapter.js +1 -247
- package/dist/adapters/PrismaAdapter.mjs +1 -220
- package/dist/adapters/SequelizeAdapter.d.mts +1 -1
- package/dist/adapters/SequelizeAdapter.d.ts +1 -1
- package/dist/adapters/SequelizeAdapter.js +1 -280
- package/dist/adapters/SequelizeAdapter.mjs +1 -260
- package/dist/adapters/TypeORMAdapter.d.mts +1 -1
- package/dist/adapters/TypeORMAdapter.d.ts +1 -1
- package/dist/adapters/TypeORMAdapter.js +1 -294
- package/dist/adapters/TypeORMAdapter.mjs +1 -267
- package/dist/chunk-7I3EYD6K.js +1 -0
- package/dist/chunk-7OR75DD2.mjs +1 -0
- package/dist/chunk-A7LUHNSI.mjs +1 -0
- package/dist/chunk-B3DBSP2J.mjs +1 -0
- package/dist/chunk-CKT6LD2K.js +1 -0
- package/dist/chunk-DBYMXDVA.mjs +1 -0
- package/dist/chunk-EEPSARTF.js +1 -0
- package/dist/chunk-ISSVD3WP.js +1 -0
- package/dist/chunk-LLYCNTZ7.mjs +1 -0
- package/dist/chunk-OKPAWWZN.js +1 -0
- package/dist/chunk-Z6WU7X3A.mjs +1 -0
- package/dist/chunk-ZJ2WW53U.js +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1514
- package/dist/index.mjs +1 -1450
- package/package.json +5 -3
- package/dist/adapters/KnexAdapter.js.map +0 -1
- package/dist/adapters/KnexAdapter.mjs.map +0 -1
- package/dist/adapters/MongooseAdapter.js.map +0 -1
- package/dist/adapters/MongooseAdapter.mjs.map +0 -1
- package/dist/adapters/PrismaAdapter.js.map +0 -1
- package/dist/adapters/PrismaAdapter.mjs.map +0 -1
- package/dist/adapters/SequelizeAdapter.js.map +0 -1
- package/dist/adapters/SequelizeAdapter.mjs.map +0 -1
- package/dist/adapters/TypeORMAdapter.js.map +0 -1
- package/dist/adapters/TypeORMAdapter.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,1450 +1 @@
|
|
|
1
|
-
var
|
|
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};
|