@contentrain/query 3.0.0 → 3.2.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/index.d.mts +45 -26
- package/dist/index.d.ts +45 -26
- package/dist/index.js +366 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +367 -65
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -3,9 +3,25 @@
|
|
|
3
3
|
var promises = require('fs/promises');
|
|
4
4
|
var path = require('path');
|
|
5
5
|
var tinyLru = require('tiny-lru');
|
|
6
|
+
var process = require('process');
|
|
6
7
|
|
|
7
8
|
var __defProp = Object.defineProperty;
|
|
8
9
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
+
var isDevelopment = process.env.NODE_ENV === "development";
|
|
11
|
+
var logger = {
|
|
12
|
+
debug: /* @__PURE__ */ __name((...args) => {
|
|
13
|
+
if (isDevelopment) {
|
|
14
|
+
console.log(...args);
|
|
15
|
+
}
|
|
16
|
+
}, "debug"),
|
|
17
|
+
error: /* @__PURE__ */ __name((...args) => {
|
|
18
|
+
if (isDevelopment) {
|
|
19
|
+
console.error(...args);
|
|
20
|
+
}
|
|
21
|
+
}, "error")
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/cache/memory.ts
|
|
9
25
|
var _MemoryCache = class _MemoryCache {
|
|
10
26
|
constructor(options = {}) {
|
|
11
27
|
this.stats = {
|
|
@@ -29,6 +45,10 @@ var _MemoryCache = class _MemoryCache {
|
|
|
29
45
|
return new TextEncoder().encode(str).length;
|
|
30
46
|
}
|
|
31
47
|
async set(key, data, ttl) {
|
|
48
|
+
logger.debug("Saving data to cache:", {
|
|
49
|
+
key,
|
|
50
|
+
ttl
|
|
51
|
+
});
|
|
32
52
|
await this.cleanupCache();
|
|
33
53
|
const size = this.calculateSize(data);
|
|
34
54
|
const now = Date.now();
|
|
@@ -51,6 +71,10 @@ var _MemoryCache = class _MemoryCache {
|
|
|
51
71
|
}
|
|
52
72
|
this.cache.set(key, entry);
|
|
53
73
|
this.stats.size += size;
|
|
74
|
+
logger.debug("Data saved to cache:", {
|
|
75
|
+
key,
|
|
76
|
+
expiry: expireAt ? new Date(expireAt).toISOString() : "no expiry"
|
|
77
|
+
});
|
|
54
78
|
}
|
|
55
79
|
findOldestKey() {
|
|
56
80
|
let oldestKey = null;
|
|
@@ -65,8 +89,10 @@ var _MemoryCache = class _MemoryCache {
|
|
|
65
89
|
return oldestKey;
|
|
66
90
|
}
|
|
67
91
|
async get(key) {
|
|
92
|
+
logger.debug("Getting data from cache:", { key });
|
|
68
93
|
const entry = this.cache.get(key);
|
|
69
94
|
if (!entry) {
|
|
95
|
+
logger.debug("Data not found in cache:", { key });
|
|
70
96
|
this.stats.misses++;
|
|
71
97
|
return null;
|
|
72
98
|
}
|
|
@@ -75,17 +101,24 @@ var _MemoryCache = class _MemoryCache {
|
|
|
75
101
|
this.stats.misses++;
|
|
76
102
|
return null;
|
|
77
103
|
}
|
|
104
|
+
logger.debug("Data retrieved from cache:", {
|
|
105
|
+
key,
|
|
106
|
+
expiry: entry.expireAt ? new Date(entry.expireAt).toISOString() : "no expiry"
|
|
107
|
+
});
|
|
78
108
|
this.stats.hits++;
|
|
79
109
|
return entry.data;
|
|
80
110
|
}
|
|
81
111
|
async delete(key) {
|
|
112
|
+
logger.debug("Deleting data from cache:", { key });
|
|
82
113
|
const entry = this.cache.get(key);
|
|
83
114
|
if (entry) {
|
|
84
115
|
this.stats.size -= entry.size;
|
|
85
116
|
this.cache.delete(key);
|
|
86
117
|
}
|
|
118
|
+
logger.debug("Data deleted from cache:", { key });
|
|
87
119
|
}
|
|
88
120
|
async clear() {
|
|
121
|
+
logger.debug("Clearing cache");
|
|
89
122
|
this.cache.clear();
|
|
90
123
|
this.stats = {
|
|
91
124
|
hits: 0,
|
|
@@ -93,6 +126,7 @@ var _MemoryCache = class _MemoryCache {
|
|
|
93
126
|
size: 0,
|
|
94
127
|
lastCleanup: Date.now()
|
|
95
128
|
};
|
|
129
|
+
logger.debug("Cache cleared");
|
|
96
130
|
}
|
|
97
131
|
async cleanupCache() {
|
|
98
132
|
const now = Date.now();
|
|
@@ -135,7 +169,7 @@ var _ContentLoader = class _ContentLoader {
|
|
|
135
169
|
defaultLocale: "en",
|
|
136
170
|
cache: true,
|
|
137
171
|
ttl: 60 * 1e3,
|
|
138
|
-
// 1
|
|
172
|
+
// 1 minute
|
|
139
173
|
maxCacheSize: 100,
|
|
140
174
|
// 100 MB
|
|
141
175
|
...options
|
|
@@ -169,33 +203,46 @@ var _ContentLoader = class _ContentLoader {
|
|
|
169
203
|
const allMetadata = JSON.parse(metadataContent);
|
|
170
204
|
const modelMetadata = allMetadata.find((m) => m.modelId === model);
|
|
171
205
|
if (!modelMetadata) {
|
|
206
|
+
logger.error("Model metadata not found:", {
|
|
207
|
+
model,
|
|
208
|
+
metadataPath
|
|
209
|
+
});
|
|
172
210
|
throw new Error(`Model metadata not found for ${model}`);
|
|
173
211
|
}
|
|
174
212
|
const modelPath = path.join(this.options.contentDir, "models", `${model}.json`);
|
|
175
213
|
const modelContent = await promises.readFile(modelPath, "utf-8");
|
|
176
214
|
const modelFields = JSON.parse(modelContent);
|
|
177
|
-
if (!Array.isArray(modelFields)) {
|
|
178
|
-
throw new TypeError(`Invalid field configuration for model ${model}: Expected an array of fields`);
|
|
179
|
-
}
|
|
180
|
-
modelFields.forEach((field, index) => {
|
|
181
|
-
if (!field.fieldId || !field.fieldType || !field.componentId) {
|
|
182
|
-
throw new Error(`Invalid field at index ${index} for model ${model}: Missing required properties`);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
215
|
return {
|
|
186
216
|
metadata: modelMetadata,
|
|
187
217
|
fields: modelFields
|
|
188
218
|
};
|
|
189
219
|
} catch (error) {
|
|
220
|
+
logger.error("Failed to load model config:", {
|
|
221
|
+
model,
|
|
222
|
+
error: error?.message || "Unknown error"
|
|
223
|
+
});
|
|
190
224
|
throw new Error(`Failed to load model config for ${model}: ${error?.message || "Unknown error"}`);
|
|
191
225
|
}
|
|
192
226
|
}
|
|
193
|
-
async loadContentFile(model, locale) {
|
|
227
|
+
async loadContentFile(model, locale = "default") {
|
|
194
228
|
try {
|
|
229
|
+
const modelConfig = await this.loadModelConfig(model);
|
|
195
230
|
let contentPath;
|
|
196
|
-
if (
|
|
231
|
+
if (modelConfig.metadata.localization) {
|
|
232
|
+
if (!locale || locale === "default") {
|
|
233
|
+
if (!this.options.defaultLocale) {
|
|
234
|
+
logger.error("Default locale is required for localized model:", {
|
|
235
|
+
model
|
|
236
|
+
});
|
|
237
|
+
throw new Error(`Default locale is required for localized model "${model}"`);
|
|
238
|
+
}
|
|
239
|
+
locale = this.options.defaultLocale;
|
|
240
|
+
}
|
|
197
241
|
contentPath = path.join(this.options.contentDir, model, `${locale}.json`);
|
|
198
242
|
} else {
|
|
243
|
+
if (locale !== "default") {
|
|
244
|
+
console.warn(`Locale "${locale}" specified for non-localized model "${model}". This parameter will be ignored.`);
|
|
245
|
+
}
|
|
199
246
|
contentPath = path.join(this.options.contentDir, model, `${model}.json`);
|
|
200
247
|
}
|
|
201
248
|
const content = await promises.readFile(contentPath, "utf-8");
|
|
@@ -203,14 +250,23 @@ var _ContentLoader = class _ContentLoader {
|
|
|
203
250
|
const data = JSON.parse(content);
|
|
204
251
|
return {
|
|
205
252
|
model,
|
|
206
|
-
locale,
|
|
253
|
+
locale: modelConfig.metadata.localization ? locale : void 0,
|
|
207
254
|
data
|
|
208
255
|
};
|
|
209
256
|
} catch {
|
|
257
|
+
logger.error("Failed to load content:", {
|
|
258
|
+
model,
|
|
259
|
+
locale,
|
|
260
|
+
contentPath
|
|
261
|
+
});
|
|
210
262
|
throw new Error(`Failed to load content: Invalid JSON format in ${contentPath}`);
|
|
211
263
|
}
|
|
212
264
|
} catch (error) {
|
|
213
265
|
if (error.message.includes("Invalid JSON format")) {
|
|
266
|
+
logger.error("Failed to load content:", {
|
|
267
|
+
model,
|
|
268
|
+
locale
|
|
269
|
+
});
|
|
214
270
|
throw error;
|
|
215
271
|
}
|
|
216
272
|
throw new Error(
|
|
@@ -243,8 +299,31 @@ var _ContentLoader = class _ContentLoader {
|
|
|
243
299
|
throw new Error(`Failed to load relations for ${model}: ${error?.message || "Unknown error"}`);
|
|
244
300
|
}
|
|
245
301
|
}
|
|
302
|
+
async getModelLocales(model, modelConfig) {
|
|
303
|
+
try {
|
|
304
|
+
if (!modelConfig.metadata.localization) {
|
|
305
|
+
return ["default"];
|
|
306
|
+
}
|
|
307
|
+
const modelDir = path.join(this.options.contentDir, model);
|
|
308
|
+
const files = await promises.readdir(modelDir);
|
|
309
|
+
const locales = files.filter((file) => file.endsWith(".json")).map((file) => file.replace(".json", "")).filter((locale) => locale !== model);
|
|
310
|
+
if (locales.length === 0) {
|
|
311
|
+
if (!this.options.defaultLocale) {
|
|
312
|
+
throw new Error(`No locale files found for localized model "${model}" and no default locale specified`);
|
|
313
|
+
}
|
|
314
|
+
return [this.options.defaultLocale];
|
|
315
|
+
}
|
|
316
|
+
return locales;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (!this.options.defaultLocale) {
|
|
319
|
+
throw new Error(`Failed to read locales for model ${model} and no default locale specified: ${error?.message}`);
|
|
320
|
+
}
|
|
321
|
+
console.warn(`Failed to read locales for model ${model}: ${error?.message}`);
|
|
322
|
+
return [this.options.defaultLocale];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
246
325
|
async load(model) {
|
|
247
|
-
const cacheKey =
|
|
326
|
+
const cacheKey = `${model}`;
|
|
248
327
|
if (this.options.cache) {
|
|
249
328
|
const cached = await this.cache.get(cacheKey);
|
|
250
329
|
if (cached)
|
|
@@ -256,18 +335,34 @@ var _ContentLoader = class _ContentLoader {
|
|
|
256
335
|
this.relations.set(model, relations);
|
|
257
336
|
const content = {};
|
|
258
337
|
if (modelConfig.metadata.localization) {
|
|
259
|
-
const locales =
|
|
338
|
+
const locales = await this.getModelLocales(model, modelConfig);
|
|
260
339
|
for (const locale of locales) {
|
|
261
|
-
|
|
262
|
-
|
|
340
|
+
try {
|
|
341
|
+
const file = await this.loadContentFile(model, locale);
|
|
342
|
+
content[locale] = file.data;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.warn(`Failed to load content for locale ${locale}: ${error?.message}`);
|
|
345
|
+
if (locale === this.options.defaultLocale) {
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
263
349
|
}
|
|
264
350
|
} else {
|
|
265
351
|
const file = await this.loadContentFile(model);
|
|
266
352
|
content.default = file.data;
|
|
267
353
|
}
|
|
354
|
+
let assets = [];
|
|
355
|
+
try {
|
|
356
|
+
const assetsPath = path.join(this.options.contentDir, "assets.json");
|
|
357
|
+
const assetsContent = await promises.readFile(assetsPath, "utf-8");
|
|
358
|
+
assets = JSON.parse(assetsContent);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.warn("Assets file not found or cannot be read:", error);
|
|
361
|
+
}
|
|
268
362
|
const result = {
|
|
269
363
|
model: modelConfig,
|
|
270
|
-
content
|
|
364
|
+
content,
|
|
365
|
+
assets
|
|
271
366
|
};
|
|
272
367
|
if (this.options.cache) {
|
|
273
368
|
const ttl = this.getModelTTL(model);
|
|
@@ -277,19 +372,65 @@ var _ContentLoader = class _ContentLoader {
|
|
|
277
372
|
}
|
|
278
373
|
async resolveRelation(model, relationField, data, locale) {
|
|
279
374
|
try {
|
|
375
|
+
logger.debug("Debug - Starting relation resolution:", {
|
|
376
|
+
model,
|
|
377
|
+
relationField,
|
|
378
|
+
dataLength: data.length,
|
|
379
|
+
locale
|
|
380
|
+
});
|
|
280
381
|
const relations = this.relations.get(model);
|
|
382
|
+
logger.debug("Debug - Relations:", relations);
|
|
281
383
|
if (!relations)
|
|
282
384
|
throw new Error(`No relations found for model: ${model}`);
|
|
283
385
|
const relation = relations.find((r) => r.foreignKey === relationField);
|
|
386
|
+
logger.debug("Debug - Found relation:", relation);
|
|
284
387
|
if (!relation)
|
|
285
388
|
throw new Error(`No relation found for field: ${String(relationField)}`);
|
|
389
|
+
logger.debug("Debug - Related model loading:", relation.model);
|
|
286
390
|
const relatedContent = await this.load(relation.model);
|
|
287
|
-
|
|
391
|
+
logger.debug("Debug - \u0130li\u015Fkili model y\xFCklendi:", {
|
|
392
|
+
model: relation.model,
|
|
393
|
+
metadata: relatedContent.model.metadata,
|
|
394
|
+
contentKeys: Object.keys(relatedContent.content)
|
|
395
|
+
});
|
|
396
|
+
let relatedData;
|
|
397
|
+
if (relatedContent.model.metadata.localization) {
|
|
398
|
+
logger.debug("Debug - Processing localized model");
|
|
399
|
+
const localizedContent = locale ? relatedContent.content[locale] : relatedContent.content.en;
|
|
400
|
+
logger.debug("Debug - Localized content:", {
|
|
401
|
+
locale: locale || "en",
|
|
402
|
+
contentType: typeof localizedContent,
|
|
403
|
+
isArray: Array.isArray(localizedContent)
|
|
404
|
+
});
|
|
405
|
+
if (!Array.isArray(localizedContent)) {
|
|
406
|
+
throw new TypeError(`Invalid content format for localized model ${relation.model}`);
|
|
407
|
+
}
|
|
408
|
+
relatedData = localizedContent;
|
|
409
|
+
} else {
|
|
410
|
+
logger.debug("Debug - Processing non-localized model");
|
|
411
|
+
const nonLocalizedContent = relatedContent.content.default;
|
|
412
|
+
logger.debug("Debug - Raw content:", {
|
|
413
|
+
contentType: typeof nonLocalizedContent,
|
|
414
|
+
isArray: Array.isArray(nonLocalizedContent),
|
|
415
|
+
content: nonLocalizedContent
|
|
416
|
+
});
|
|
417
|
+
if (!Array.isArray(nonLocalizedContent)) {
|
|
418
|
+
throw new TypeError(`Invalid content format for non-localized model ${relation.model}`);
|
|
419
|
+
}
|
|
420
|
+
relatedData = nonLocalizedContent;
|
|
421
|
+
}
|
|
422
|
+
logger.debug("Debug - Related data ready:", {
|
|
423
|
+
dataLength: relatedData.length,
|
|
424
|
+
firstItem: relatedData[0]
|
|
425
|
+
});
|
|
288
426
|
if (!relatedData) {
|
|
289
427
|
throw new Error(`Failed to resolve relation: No data found for model ${relation.model}`);
|
|
290
428
|
}
|
|
291
429
|
if (relation.type === "one-to-one") {
|
|
292
|
-
|
|
430
|
+
logger.debug("Debug - Processing one-to-one relation");
|
|
431
|
+
const itemsWithRelation = data.filter((item) => item[relationField] !== void 0);
|
|
432
|
+
logger.debug("Debug - Items with relations:", itemsWithRelation.length);
|
|
433
|
+
return itemsWithRelation.map((item) => {
|
|
293
434
|
const relatedItem = relatedData.find((r) => r.ID === item[relationField]);
|
|
294
435
|
if (!relatedItem) {
|
|
295
436
|
throw new Error(`Failed to resolve relation: No matching item found for ID ${String(item[relationField])}`);
|
|
@@ -297,16 +438,22 @@ var _ContentLoader = class _ContentLoader {
|
|
|
297
438
|
return relatedItem;
|
|
298
439
|
});
|
|
299
440
|
} else {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
441
|
+
logger.debug("Debug - Processing one-to-many relation");
|
|
442
|
+
const uniqueIds = new Set(
|
|
443
|
+
data.flatMap(
|
|
444
|
+
(item) => item[relationField] !== void 0 ? Array.isArray(item[relationField]) ? item[relationField] : [item[relationField]] : []
|
|
445
|
+
)
|
|
446
|
+
);
|
|
447
|
+
logger.debug("Debug - Unique IDs:", Array.from(uniqueIds));
|
|
448
|
+
const items = Array.from(uniqueIds).map((id) => relatedData.find((r) => r.ID === id)).filter(Boolean);
|
|
449
|
+
logger.debug("Debug - Matching items:", items.length);
|
|
450
|
+
if (items.length !== uniqueIds.size) {
|
|
451
|
+
throw new Error("Failed to resolve relation: Some related items not found");
|
|
452
|
+
}
|
|
453
|
+
return items;
|
|
308
454
|
}
|
|
309
455
|
} catch (error) {
|
|
456
|
+
logger.error("Debug - Error occurred:", error);
|
|
310
457
|
throw new Error(`Failed to resolve relation: ${error.message}`);
|
|
311
458
|
}
|
|
312
459
|
}
|
|
@@ -327,6 +474,11 @@ var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
|
|
|
327
474
|
this.loader = loader;
|
|
328
475
|
}
|
|
329
476
|
where(field, operator, value) {
|
|
477
|
+
logger.debug("Adding filter:", {
|
|
478
|
+
field,
|
|
479
|
+
operator,
|
|
480
|
+
value
|
|
481
|
+
});
|
|
330
482
|
this.filters.push({
|
|
331
483
|
field,
|
|
332
484
|
operator,
|
|
@@ -335,6 +487,7 @@ var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
|
|
|
335
487
|
return this;
|
|
336
488
|
}
|
|
337
489
|
include(relation) {
|
|
490
|
+
logger.debug("Adding relation:", relation);
|
|
338
491
|
if (typeof relation === "string") {
|
|
339
492
|
this.includes[relation] = {};
|
|
340
493
|
} else if (Array.isArray(relation)) {
|
|
@@ -345,6 +498,10 @@ var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
|
|
|
345
498
|
return this;
|
|
346
499
|
}
|
|
347
500
|
orderBy(field, direction = "asc") {
|
|
501
|
+
logger.debug("Adding sorting:", {
|
|
502
|
+
field,
|
|
503
|
+
direction
|
|
504
|
+
});
|
|
348
505
|
this.sorting.push({
|
|
349
506
|
field,
|
|
350
507
|
direction
|
|
@@ -352,28 +509,37 @@ var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
|
|
|
352
509
|
return this;
|
|
353
510
|
}
|
|
354
511
|
limit(count) {
|
|
512
|
+
logger.debug("Adding limit:", count);
|
|
355
513
|
this.pagination.limit = count;
|
|
356
514
|
return this;
|
|
357
515
|
}
|
|
358
516
|
offset(count) {
|
|
517
|
+
logger.debug("Adding offset:", count);
|
|
359
518
|
this.pagination.offset = count;
|
|
360
519
|
return this;
|
|
361
520
|
}
|
|
362
521
|
locale(code) {
|
|
522
|
+
logger.debug("Setting locale:", code);
|
|
363
523
|
this.options.locale = code;
|
|
364
524
|
return this;
|
|
365
525
|
}
|
|
366
526
|
cache(ttl) {
|
|
527
|
+
logger.debug("Setting cache:", {
|
|
528
|
+
enabled: true,
|
|
529
|
+
ttl
|
|
530
|
+
});
|
|
367
531
|
this.options.cache = true;
|
|
368
532
|
if (ttl)
|
|
369
533
|
this.options.ttl = ttl;
|
|
370
534
|
return this;
|
|
371
535
|
}
|
|
372
536
|
noCache() {
|
|
537
|
+
logger.debug("Disabling cache");
|
|
373
538
|
this.options.cache = false;
|
|
374
539
|
return this;
|
|
375
540
|
}
|
|
376
541
|
bypassCache() {
|
|
542
|
+
logger.debug("Bypassing cache");
|
|
377
543
|
this.options.cache = false;
|
|
378
544
|
this.options.ttl = 0;
|
|
379
545
|
return this;
|
|
@@ -389,8 +555,56 @@ var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
|
|
|
389
555
|
};
|
|
390
556
|
}
|
|
391
557
|
async get() {
|
|
558
|
+
logger.debug("Starting query:", {
|
|
559
|
+
model: this.model,
|
|
560
|
+
filterCount: this.filters.length,
|
|
561
|
+
includeCount: Object.keys(this.includes).length,
|
|
562
|
+
sortingCount: this.sorting.length,
|
|
563
|
+
pagination: this.pagination,
|
|
564
|
+
options: this.options
|
|
565
|
+
});
|
|
392
566
|
const result = await this.loader.load(this.model);
|
|
393
|
-
const
|
|
567
|
+
const modelConfig = result.model;
|
|
568
|
+
logger.debug("Model loaded:", {
|
|
569
|
+
model: this.model,
|
|
570
|
+
metadata: modelConfig.metadata,
|
|
571
|
+
contentKeys: Object.keys(result.content)
|
|
572
|
+
});
|
|
573
|
+
let data;
|
|
574
|
+
if (modelConfig.metadata.localization) {
|
|
575
|
+
const locale = this.options.locale || "en";
|
|
576
|
+
logger.debug("Selecting content for localized model:", {
|
|
577
|
+
model: this.model,
|
|
578
|
+
requestedLocale: locale,
|
|
579
|
+
availableLocales: Object.keys(result.content)
|
|
580
|
+
});
|
|
581
|
+
data = result.content[locale];
|
|
582
|
+
if (!data) {
|
|
583
|
+
logger.error("Content not found:", {
|
|
584
|
+
model: this.model,
|
|
585
|
+
locale,
|
|
586
|
+
availableLocales: Object.keys(result.content)
|
|
587
|
+
});
|
|
588
|
+
throw new Error(`Content not found for locale: ${locale}`);
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
logger.debug("Selecting content for non-localized model:", {
|
|
592
|
+
model: this.model,
|
|
593
|
+
contentKeys: Object.keys(result.content)
|
|
594
|
+
});
|
|
595
|
+
if (!result.content.default) {
|
|
596
|
+
logger.error("Content not found:", {
|
|
597
|
+
model: this.model,
|
|
598
|
+
contentKeys: Object.keys(result.content)
|
|
599
|
+
});
|
|
600
|
+
throw new Error(`Content not found for model: ${this.model}`);
|
|
601
|
+
}
|
|
602
|
+
data = result.content.default;
|
|
603
|
+
}
|
|
604
|
+
logger.debug("Executing query:", {
|
|
605
|
+
model: this.model,
|
|
606
|
+
dataLength: data.length
|
|
607
|
+
});
|
|
394
608
|
return this.executor.execute({
|
|
395
609
|
model: this.model,
|
|
396
610
|
data,
|
|
@@ -419,51 +633,80 @@ var _QueryExecutor = class _QueryExecutor {
|
|
|
419
633
|
this.loader = loader;
|
|
420
634
|
}
|
|
421
635
|
applyFilters(data, filters) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
636
|
+
logger.debug("Starting to apply filters:", {
|
|
637
|
+
dataLength: data.length,
|
|
638
|
+
filters
|
|
639
|
+
});
|
|
640
|
+
const result = data.filter((item) => {
|
|
641
|
+
return filters.every(({ field, operator, value }) => {
|
|
642
|
+
const itemValue = item[field];
|
|
425
643
|
const validOperators = ["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "contains", "startsWith", "endsWith"];
|
|
426
|
-
if (!validOperators.includes(
|
|
427
|
-
|
|
644
|
+
if (!validOperators.includes(operator)) {
|
|
645
|
+
logger.error("Invalid operator:", operator);
|
|
646
|
+
throw new Error(`Invalid operator: ${operator}`);
|
|
647
|
+
}
|
|
648
|
+
if (typeof itemValue === "string" && typeof value === "string") {
|
|
649
|
+
return this.applyStringOperation(itemValue, operator, value);
|
|
650
|
+
}
|
|
651
|
+
if (Array.isArray(value)) {
|
|
652
|
+
switch (operator) {
|
|
653
|
+
case "in":
|
|
654
|
+
return value.includes(itemValue);
|
|
655
|
+
case "nin":
|
|
656
|
+
return !value.includes(itemValue);
|
|
657
|
+
default:
|
|
658
|
+
logger.error("Invalid array operator:", operator);
|
|
659
|
+
throw new Error(`Invalid array operator: ${operator}`);
|
|
660
|
+
}
|
|
428
661
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
662
|
+
if (Array.isArray(itemValue)) {
|
|
663
|
+
switch (operator) {
|
|
664
|
+
case "in":
|
|
665
|
+
return value.some((v) => itemValue.includes(v));
|
|
666
|
+
case "nin":
|
|
667
|
+
return !value.some((v) => itemValue.includes(v));
|
|
668
|
+
default:
|
|
669
|
+
logger.error("Invalid array operator:", operator);
|
|
670
|
+
throw new Error(`Invalid array operator: ${operator}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (typeof itemValue === "number" && typeof value === "number") {
|
|
674
|
+
switch (operator) {
|
|
675
|
+
case "eq":
|
|
676
|
+
return itemValue === value;
|
|
677
|
+
case "ne":
|
|
678
|
+
return itemValue !== value;
|
|
679
|
+
case "gt":
|
|
680
|
+
return itemValue > value;
|
|
681
|
+
case "gte":
|
|
682
|
+
return itemValue >= value;
|
|
683
|
+
case "lt":
|
|
684
|
+
return itemValue < value;
|
|
685
|
+
case "lte":
|
|
686
|
+
return itemValue <= value;
|
|
687
|
+
}
|
|
454
688
|
}
|
|
689
|
+
return false;
|
|
455
690
|
});
|
|
456
691
|
});
|
|
692
|
+
logger.debug("Filter application completed:", {
|
|
693
|
+
initialCount: data.length,
|
|
694
|
+
resultCount: result.length
|
|
695
|
+
});
|
|
696
|
+
return result;
|
|
457
697
|
}
|
|
458
698
|
applySorting(data, sorting) {
|
|
459
699
|
return [...data].sort((a, b) => {
|
|
460
|
-
for (const
|
|
461
|
-
|
|
462
|
-
|
|
700
|
+
for (const { field, direction } of sorting) {
|
|
701
|
+
if (!(field in a)) {
|
|
702
|
+
throw new Error(`Invalid sort field: ${field}`);
|
|
703
|
+
}
|
|
704
|
+
const aValue = a[field];
|
|
705
|
+
const bValue = b[field];
|
|
463
706
|
if (aValue === bValue)
|
|
464
707
|
continue;
|
|
465
|
-
const
|
|
466
|
-
return
|
|
708
|
+
const compareResult = aValue < bValue ? -1 : 1;
|
|
709
|
+
return direction === "asc" ? compareResult : -compareResult;
|
|
467
710
|
}
|
|
468
711
|
return 0;
|
|
469
712
|
});
|
|
@@ -474,15 +717,26 @@ var _QueryExecutor = class _QueryExecutor {
|
|
|
474
717
|
return data.slice(offset, offset + limit);
|
|
475
718
|
}
|
|
476
719
|
async resolveIncludes(model, data, includes, options) {
|
|
720
|
+
logger.debug("Starting to resolve relations:", {
|
|
721
|
+
model,
|
|
722
|
+
dataLength: data.length,
|
|
723
|
+
includes,
|
|
724
|
+
options
|
|
725
|
+
});
|
|
477
726
|
const result = [...data];
|
|
478
727
|
for (const [field, config] of Object.entries(includes)) {
|
|
728
|
+
logger.debug(`Resolving relation "${field}"`);
|
|
479
729
|
const relations = await this.loader.resolveRelation(
|
|
480
730
|
model,
|
|
481
731
|
field,
|
|
482
732
|
result,
|
|
483
733
|
options.locale
|
|
484
734
|
);
|
|
735
|
+
logger.debug(`Relation "${field}" resolved:`, {
|
|
736
|
+
foundRelationsCount: relations.length
|
|
737
|
+
});
|
|
485
738
|
if (config.include && relations.length) {
|
|
739
|
+
logger.debug(`Resolving nested relations for "${field}":`, config.include);
|
|
486
740
|
await this.resolveIncludes(
|
|
487
741
|
field,
|
|
488
742
|
relations,
|
|
@@ -503,9 +757,28 @@ var _QueryExecutor = class _QueryExecutor {
|
|
|
503
757
|
}
|
|
504
758
|
item._relations[field] = Array.isArray(value) ? relatedItems : relatedItems[0];
|
|
505
759
|
});
|
|
760
|
+
logger.debug(`Data added for relation "${field}"`);
|
|
506
761
|
}
|
|
507
762
|
return result;
|
|
508
763
|
}
|
|
764
|
+
applyStringOperation(value, operator, searchValue) {
|
|
765
|
+
switch (operator) {
|
|
766
|
+
case "eq":
|
|
767
|
+
return value === searchValue;
|
|
768
|
+
case "ne":
|
|
769
|
+
return value !== searchValue;
|
|
770
|
+
case "contains":
|
|
771
|
+
return value.toLowerCase().includes(searchValue.toLowerCase());
|
|
772
|
+
case "startsWith":
|
|
773
|
+
return value.toLowerCase().startsWith(searchValue.toLowerCase());
|
|
774
|
+
case "endsWith":
|
|
775
|
+
return value.toLowerCase().endsWith(searchValue.toLowerCase());
|
|
776
|
+
default: {
|
|
777
|
+
const _exhaustiveCheck = operator;
|
|
778
|
+
return _exhaustiveCheck;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
509
782
|
async execute({
|
|
510
783
|
model,
|
|
511
784
|
data,
|
|
@@ -515,17 +788,37 @@ var _QueryExecutor = class _QueryExecutor {
|
|
|
515
788
|
pagination = {},
|
|
516
789
|
options = {}
|
|
517
790
|
}) {
|
|
791
|
+
logger.debug("Starting execution:", {
|
|
792
|
+
model,
|
|
793
|
+
dataLength: data.length,
|
|
794
|
+
filterCount: filters.length,
|
|
795
|
+
includeCount: Object.keys(includes).length,
|
|
796
|
+
sortingCount: sorting.length,
|
|
797
|
+
pagination,
|
|
798
|
+
options
|
|
799
|
+
});
|
|
518
800
|
let result = [...data];
|
|
519
801
|
if (filters.length) {
|
|
802
|
+
logger.debug("Applying filters:", filters);
|
|
520
803
|
result = this.applyFilters(result, filters);
|
|
804
|
+
logger.debug("Remaining items after filtering:", result.length);
|
|
521
805
|
}
|
|
522
806
|
if (Object.keys(includes).length) {
|
|
807
|
+
logger.debug("Resolving relations:", includes);
|
|
523
808
|
result = await this.resolveIncludes(model, result, includes, options);
|
|
809
|
+
logger.debug("Items after relation resolution:", result.length);
|
|
524
810
|
}
|
|
525
811
|
if (sorting.length) {
|
|
812
|
+
logger.debug("Applying sorting:", sorting);
|
|
526
813
|
result = this.applySorting(result, sorting);
|
|
527
814
|
}
|
|
528
815
|
const paginatedData = this.applyPagination(result, pagination.limit, pagination.offset);
|
|
816
|
+
logger.debug("After pagination:", {
|
|
817
|
+
totalCount: result.length,
|
|
818
|
+
pageSize: paginatedData.length,
|
|
819
|
+
offset: pagination.offset || 0,
|
|
820
|
+
hasMore: (pagination.offset || 0) + paginatedData.length < result.length
|
|
821
|
+
});
|
|
529
822
|
return {
|
|
530
823
|
data: paginatedData,
|
|
531
824
|
total: result.length,
|
|
@@ -556,6 +849,15 @@ var _ContentrainSDK = class _ContentrainSDK {
|
|
|
556
849
|
async load(model) {
|
|
557
850
|
return this.loader.load(model);
|
|
558
851
|
}
|
|
852
|
+
async clearCache() {
|
|
853
|
+
return this.loader.clearCache();
|
|
854
|
+
}
|
|
855
|
+
async refreshCache(model) {
|
|
856
|
+
return this.loader.refreshCache(model);
|
|
857
|
+
}
|
|
858
|
+
getCacheStats() {
|
|
859
|
+
return this.loader.getCacheStats();
|
|
860
|
+
}
|
|
559
861
|
};
|
|
560
862
|
__name(_ContentrainSDK, "ContentrainSDK");
|
|
561
863
|
var ContentrainSDK = _ContentrainSDK;
|
|
@@ -565,5 +867,6 @@ exports.ContentrainQueryBuilder = ContentrainQueryBuilder;
|
|
|
565
867
|
exports.ContentrainSDK = ContentrainSDK;
|
|
566
868
|
exports.MemoryCache = MemoryCache;
|
|
567
869
|
exports.QueryExecutor = QueryExecutor;
|
|
870
|
+
exports.logger = logger;
|
|
568
871
|
//# sourceMappingURL=index.js.map
|
|
569
872
|
//# sourceMappingURL=index.js.map
|