@contentrain/query 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +220 -86
- package/dist/index.d.mts +270 -26
- package/dist/index.d.ts +270 -26
- package/dist/index.js +534 -175
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +530 -152
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -14
- package/CHANGELOG.md +0 -27
- package/src/index.test.ts +0 -150
- package/src/index.ts +0 -233
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -8
- package/vitest.config.ts +0 -8
package/dist/index.mjs
CHANGED
|
@@ -1,185 +1,563 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { lru } from 'tiny-lru';
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var _MemoryCache = class _MemoryCache {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.stats = {
|
|
10
|
+
hits: 0,
|
|
11
|
+
misses: 0,
|
|
12
|
+
size: 0,
|
|
13
|
+
lastCleanup: Date.now()
|
|
14
|
+
};
|
|
15
|
+
this.options = {
|
|
16
|
+
maxSize: 100,
|
|
17
|
+
// 100 MB
|
|
18
|
+
defaultTTL: 60 * 1e3,
|
|
19
|
+
// 1 dakika
|
|
20
|
+
...options
|
|
21
|
+
};
|
|
22
|
+
const maxItems = Math.floor(this.options.maxSize * 1024 * 1024 / 1e3);
|
|
23
|
+
this.cache = lru(maxItems);
|
|
16
24
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
25
|
+
calculateSize(data) {
|
|
26
|
+
const str = JSON.stringify(data);
|
|
27
|
+
return new TextEncoder().encode(str).length;
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
|
|
29
|
+
async set(key, data, ttl) {
|
|
30
|
+
await this.cleanupCache();
|
|
31
|
+
const size = this.calculateSize(data);
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const expireAt = now + (ttl || this.options.defaultTTL);
|
|
34
|
+
while (size + this.stats.size > this.options.maxSize * 1024 * 1024) {
|
|
35
|
+
const oldestKey = this.findOldestKey();
|
|
36
|
+
if (!oldestKey)
|
|
37
|
+
break;
|
|
38
|
+
await this.delete(oldestKey);
|
|
39
|
+
}
|
|
40
|
+
const entry = {
|
|
41
|
+
data,
|
|
42
|
+
expireAt,
|
|
43
|
+
size,
|
|
44
|
+
createdAt: now
|
|
45
|
+
};
|
|
46
|
+
const oldEntry = this.cache.get(key);
|
|
47
|
+
if (oldEntry) {
|
|
48
|
+
this.stats.size -= oldEntry.size;
|
|
49
|
+
}
|
|
50
|
+
this.cache.set(key, entry);
|
|
51
|
+
this.stats.size += size;
|
|
24
52
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
findOldestKey() {
|
|
54
|
+
let oldestKey = null;
|
|
55
|
+
let oldestTime = Infinity;
|
|
56
|
+
for (const key of this.cache.keys()) {
|
|
57
|
+
const entry = this.cache.get(key);
|
|
58
|
+
if (entry.createdAt < oldestTime) {
|
|
59
|
+
oldestTime = entry.createdAt;
|
|
60
|
+
oldestKey = key;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return oldestKey;
|
|
28
64
|
}
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
65
|
+
async get(key) {
|
|
66
|
+
const entry = this.cache.get(key);
|
|
67
|
+
if (!entry) {
|
|
68
|
+
this.stats.misses++;
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (Date.now() >= entry.expireAt) {
|
|
72
|
+
await this.delete(key);
|
|
73
|
+
this.stats.misses++;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
this.stats.hits++;
|
|
77
|
+
return entry.data;
|
|
78
|
+
}
|
|
79
|
+
async delete(key) {
|
|
80
|
+
const entry = this.cache.get(key);
|
|
81
|
+
if (entry) {
|
|
82
|
+
this.stats.size -= entry.size;
|
|
83
|
+
this.cache.delete(key);
|
|
84
|
+
}
|
|
32
85
|
}
|
|
33
|
-
async
|
|
34
|
-
|
|
86
|
+
async clear() {
|
|
87
|
+
this.cache.clear();
|
|
88
|
+
this.stats = {
|
|
89
|
+
hits: 0,
|
|
90
|
+
misses: 0,
|
|
91
|
+
size: 0,
|
|
92
|
+
lastCleanup: Date.now()
|
|
93
|
+
};
|
|
35
94
|
}
|
|
36
|
-
async
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
async cleanupCache() {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const expiredKeys = [];
|
|
98
|
+
let totalSize = 0;
|
|
99
|
+
for (const key of this.cache.keys()) {
|
|
100
|
+
const entry = this.cache.get(key);
|
|
101
|
+
if (entry.expireAt <= now) {
|
|
102
|
+
expiredKeys.push(key);
|
|
103
|
+
} else {
|
|
104
|
+
totalSize += entry.size;
|
|
105
|
+
}
|
|
42
106
|
}
|
|
43
|
-
|
|
44
|
-
|
|
107
|
+
for (const key of expiredKeys) {
|
|
108
|
+
await this.delete(key);
|
|
45
109
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
110
|
+
while (totalSize > this.options.maxSize * 1024 * 1024) {
|
|
111
|
+
const oldestKey = this.findOldestKey();
|
|
112
|
+
if (!oldestKey)
|
|
113
|
+
break;
|
|
114
|
+
const entry = this.cache.get(oldestKey);
|
|
115
|
+
await this.delete(oldestKey);
|
|
116
|
+
totalSize -= entry.size;
|
|
49
117
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
118
|
+
this.stats.lastCleanup = now;
|
|
119
|
+
}
|
|
120
|
+
getStats() {
|
|
121
|
+
return { ...this.stats };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
__name(_MemoryCache, "MemoryCache");
|
|
125
|
+
var MemoryCache = _MemoryCache;
|
|
126
|
+
|
|
127
|
+
// src/loader/content.ts
|
|
128
|
+
var _ContentLoader = class _ContentLoader {
|
|
129
|
+
constructor(options) {
|
|
130
|
+
this.modelConfigs = /* @__PURE__ */ new Map();
|
|
131
|
+
this.relations = /* @__PURE__ */ new Map();
|
|
132
|
+
this.options = {
|
|
133
|
+
defaultLocale: "en",
|
|
134
|
+
cache: true,
|
|
135
|
+
ttl: 60 * 1e3,
|
|
136
|
+
// 1 dakika
|
|
137
|
+
maxCacheSize: 100,
|
|
138
|
+
// 100 MB
|
|
139
|
+
...options
|
|
140
|
+
};
|
|
141
|
+
this.cache = new MemoryCache({
|
|
142
|
+
maxSize: this.options.maxCacheSize,
|
|
143
|
+
defaultTTL: this.options.ttl
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
getCacheKey(model) {
|
|
147
|
+
return `${model}`;
|
|
148
|
+
}
|
|
149
|
+
getModelTTL(model) {
|
|
150
|
+
return this.options.modelTTL?.[model] || this.options.ttl || 0;
|
|
151
|
+
}
|
|
152
|
+
async clearCache() {
|
|
153
|
+
await this.cache.clear();
|
|
154
|
+
}
|
|
155
|
+
async refreshCache(model) {
|
|
156
|
+
const cacheKey = this.getCacheKey(model);
|
|
157
|
+
await this.cache.delete(cacheKey);
|
|
158
|
+
await this.load(model);
|
|
159
|
+
}
|
|
160
|
+
getCacheStats() {
|
|
161
|
+
return this.cache.getStats();
|
|
162
|
+
}
|
|
163
|
+
async loadModelConfig(model) {
|
|
164
|
+
try {
|
|
165
|
+
const metadataPath = join(this.options.contentDir, "models", "metadata.json");
|
|
166
|
+
const metadataContent = await readFile(metadataPath, "utf-8");
|
|
167
|
+
const allMetadata = JSON.parse(metadataContent);
|
|
168
|
+
const modelMetadata = allMetadata.find((m) => m.modelId === model);
|
|
169
|
+
if (!modelMetadata) {
|
|
170
|
+
throw new Error(`Model metadata not found for ${model}`);
|
|
171
|
+
}
|
|
172
|
+
const modelPath = join(this.options.contentDir, "models", `${model}.json`);
|
|
173
|
+
const modelContent = await readFile(modelPath, "utf-8");
|
|
174
|
+
const modelFields = JSON.parse(modelContent);
|
|
175
|
+
if (!Array.isArray(modelFields)) {
|
|
176
|
+
throw new TypeError(`Invalid field configuration for model ${model}: Expected an array of fields`);
|
|
177
|
+
}
|
|
178
|
+
modelFields.forEach((field, index) => {
|
|
179
|
+
if (!field.fieldId || !field.fieldType || !field.componentId) {
|
|
180
|
+
throw new Error(`Invalid field at index ${index} for model ${model}: Missing required properties`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
metadata: modelMetadata,
|
|
185
|
+
fields: modelFields
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new Error(`Failed to load model config for ${model}: ${error?.message || "Unknown error"}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async loadContentFile(model, locale) {
|
|
192
|
+
try {
|
|
193
|
+
let contentPath;
|
|
194
|
+
if (locale) {
|
|
195
|
+
contentPath = join(this.options.contentDir, model, `${locale}.json`);
|
|
196
|
+
} else {
|
|
197
|
+
contentPath = join(this.options.contentDir, model, `${model}.json`);
|
|
198
|
+
}
|
|
199
|
+
const content = await readFile(contentPath, "utf-8");
|
|
200
|
+
try {
|
|
201
|
+
const data = JSON.parse(content);
|
|
202
|
+
return {
|
|
203
|
+
model,
|
|
204
|
+
locale,
|
|
205
|
+
data
|
|
206
|
+
};
|
|
207
|
+
} catch {
|
|
208
|
+
throw new Error(`Failed to load content: Invalid JSON format in ${contentPath}`);
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error.message.includes("Invalid JSON format")) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Failed to load content file for ${model}${locale ? ` (${locale})` : ""}: ${error?.message || "Unknown error"}`
|
|
63
216
|
);
|
|
64
|
-
return relatedItems.filter((item2) => item2 !== null);
|
|
65
217
|
}
|
|
218
|
+
}
|
|
219
|
+
async loadRelations(model) {
|
|
66
220
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
221
|
+
const modelConfig = this.modelConfigs.get(model);
|
|
222
|
+
if (!modelConfig) {
|
|
223
|
+
throw new Error(`Model config not found for ${model}`);
|
|
224
|
+
}
|
|
225
|
+
const relationFields = modelConfig.fields.filter((field) => {
|
|
226
|
+
return field.fieldType === "relation";
|
|
227
|
+
});
|
|
228
|
+
return relationFields.map((field) => {
|
|
229
|
+
const options = field.options;
|
|
230
|
+
const reference = options?.reference?.form?.reference?.value;
|
|
231
|
+
if (!reference) {
|
|
232
|
+
throw new Error(`Reference not found for relation field: ${field.name}`);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
model: reference,
|
|
236
|
+
type: field.componentId === "one-to-one" ? "one-to-one" : "one-to-many",
|
|
237
|
+
foreignKey: field.fieldId
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
} catch (error) {
|
|
241
|
+
throw new Error(`Failed to load relations for ${model}: ${error?.message || "Unknown error"}`);
|
|
71
242
|
}
|
|
72
243
|
}
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return
|
|
79
|
-
case "neq":
|
|
80
|
-
return itemValue !== value;
|
|
81
|
-
case "gt":
|
|
82
|
-
return typeof itemValue === "number" && typeof value === "number" && itemValue > value;
|
|
83
|
-
case "gte":
|
|
84
|
-
return typeof itemValue === "number" && typeof value === "number" && itemValue >= value;
|
|
85
|
-
case "lt":
|
|
86
|
-
return typeof itemValue === "number" && typeof value === "number" && itemValue < value;
|
|
87
|
-
case "lte":
|
|
88
|
-
return typeof itemValue === "number" && typeof value === "number" && itemValue <= value;
|
|
89
|
-
case "contains":
|
|
90
|
-
return typeof itemValue === "string" && typeof value === "string" && itemValue.includes(value);
|
|
91
|
-
case "startsWith":
|
|
92
|
-
return typeof itemValue === "string" && typeof value === "string" && itemValue.startsWith(value);
|
|
93
|
-
case "endsWith":
|
|
94
|
-
return typeof itemValue === "string" && typeof value === "string" && itemValue.endsWith(value);
|
|
95
|
-
case "in":
|
|
96
|
-
return Array.isArray(value) && value.includes(itemValue);
|
|
97
|
-
case "nin":
|
|
98
|
-
return Array.isArray(value) && !value.includes(itemValue);
|
|
99
|
-
case "exists":
|
|
100
|
-
return itemValue !== void 0 && itemValue !== null;
|
|
101
|
-
case "notExists":
|
|
102
|
-
return itemValue === void 0 || itemValue === null;
|
|
103
|
-
default:
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
evaluateSort(a, b, sort) {
|
|
108
|
-
const { field, direction } = sort;
|
|
109
|
-
const aValue = a[field];
|
|
110
|
-
const bValue = b[field];
|
|
111
|
-
if (aValue === bValue) {
|
|
112
|
-
return 0;
|
|
244
|
+
async load(model) {
|
|
245
|
+
const cacheKey = this.getCacheKey(model);
|
|
246
|
+
if (this.options.cache) {
|
|
247
|
+
const cached = await this.cache.get(cacheKey);
|
|
248
|
+
if (cached)
|
|
249
|
+
return cached;
|
|
113
250
|
}
|
|
114
|
-
|
|
115
|
-
|
|
251
|
+
const modelConfig = await this.loadModelConfig(model);
|
|
252
|
+
this.modelConfigs.set(model, modelConfig);
|
|
253
|
+
const relations = await this.loadRelations(model);
|
|
254
|
+
this.relations.set(model, relations);
|
|
255
|
+
const content = {};
|
|
256
|
+
if (modelConfig.metadata.localization) {
|
|
257
|
+
const locales = ["en", "tr"];
|
|
258
|
+
for (const locale of locales) {
|
|
259
|
+
const file = await this.loadContentFile(model, locale);
|
|
260
|
+
content[locale] = file.data;
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
const file = await this.loadContentFile(model);
|
|
264
|
+
content.default = file.data;
|
|
116
265
|
}
|
|
117
|
-
|
|
118
|
-
|
|
266
|
+
const result = {
|
|
267
|
+
model: modelConfig,
|
|
268
|
+
content
|
|
269
|
+
};
|
|
270
|
+
if (this.options.cache) {
|
|
271
|
+
const ttl = this.getModelTTL(model);
|
|
272
|
+
await this.cache.set(cacheKey, result, ttl);
|
|
119
273
|
}
|
|
120
|
-
|
|
121
|
-
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
async resolveRelation(model, relationField, data, locale) {
|
|
277
|
+
try {
|
|
278
|
+
const relations = this.relations.get(model);
|
|
279
|
+
if (!relations)
|
|
280
|
+
throw new Error(`No relations found for model: ${model}`);
|
|
281
|
+
const relation = relations.find((r) => r.foreignKey === relationField);
|
|
282
|
+
if (!relation)
|
|
283
|
+
throw new Error(`No relation found for field: ${String(relationField)}`);
|
|
284
|
+
const relatedContent = await this.load(relation.model);
|
|
285
|
+
const relatedData = locale ? relatedContent.content[locale] : relatedContent.content.en;
|
|
286
|
+
if (!relatedData) {
|
|
287
|
+
throw new Error(`Failed to resolve relation: No data found for model ${relation.model}`);
|
|
288
|
+
}
|
|
289
|
+
if (relation.type === "one-to-one") {
|
|
290
|
+
return data.map((item) => {
|
|
291
|
+
const relatedItem = relatedData.find((r) => r.ID === item[relationField]);
|
|
292
|
+
if (!relatedItem) {
|
|
293
|
+
throw new Error(`Failed to resolve relation: No matching item found for ID ${String(item[relationField])}`);
|
|
294
|
+
}
|
|
295
|
+
return relatedItem;
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
return data.flatMap((item) => {
|
|
299
|
+
const ids = Array.isArray(item[relationField]) ? item[relationField] : [item[relationField]];
|
|
300
|
+
const items = ids.map((id) => relatedData.find((r) => r.ID === id)).filter(Boolean);
|
|
301
|
+
if (items.length !== ids.length) {
|
|
302
|
+
throw new Error("Failed to resolve relation: Some related items not found");
|
|
303
|
+
}
|
|
304
|
+
return items;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new Error(`Failed to resolve relation: ${error.message}`);
|
|
122
309
|
}
|
|
123
|
-
|
|
124
|
-
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
__name(_ContentLoader, "ContentLoader");
|
|
313
|
+
var ContentLoader = _ContentLoader;
|
|
314
|
+
|
|
315
|
+
// src/query/builder.ts
|
|
316
|
+
var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
|
|
317
|
+
constructor(model, executor, loader) {
|
|
318
|
+
this.filters = [];
|
|
319
|
+
this.includes = {};
|
|
320
|
+
this.sorting = [];
|
|
321
|
+
this.pagination = {};
|
|
322
|
+
this.options = {};
|
|
323
|
+
this.model = model;
|
|
324
|
+
this.executor = executor;
|
|
325
|
+
this.loader = loader;
|
|
326
|
+
}
|
|
327
|
+
where(field, operator, value) {
|
|
328
|
+
this.filters.push({
|
|
329
|
+
field,
|
|
330
|
+
operator,
|
|
331
|
+
value
|
|
332
|
+
});
|
|
333
|
+
return this;
|
|
334
|
+
}
|
|
335
|
+
include(relation) {
|
|
336
|
+
if (typeof relation === "string") {
|
|
337
|
+
this.includes[relation] = {};
|
|
338
|
+
} else if (Array.isArray(relation)) {
|
|
339
|
+
relation.forEach((r) => {
|
|
340
|
+
this.includes[r] = {};
|
|
341
|
+
});
|
|
125
342
|
}
|
|
126
|
-
return
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
orderBy(field, direction = "asc") {
|
|
346
|
+
this.sorting.push({
|
|
347
|
+
field,
|
|
348
|
+
direction
|
|
349
|
+
});
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
limit(count) {
|
|
353
|
+
this.pagination.limit = count;
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
offset(count) {
|
|
357
|
+
this.pagination.offset = count;
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
locale(code) {
|
|
361
|
+
this.options.locale = code;
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
cache(ttl) {
|
|
365
|
+
this.options.cache = true;
|
|
366
|
+
if (ttl)
|
|
367
|
+
this.options.ttl = ttl;
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
noCache() {
|
|
371
|
+
this.options.cache = false;
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
bypassCache() {
|
|
375
|
+
this.options.cache = false;
|
|
376
|
+
this.options.ttl = 0;
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
toJSON() {
|
|
380
|
+
return {
|
|
381
|
+
model: this.model,
|
|
382
|
+
filters: this.filters,
|
|
383
|
+
includes: this.includes,
|
|
384
|
+
sorting: this.sorting,
|
|
385
|
+
pagination: this.pagination,
|
|
386
|
+
options: this.options
|
|
387
|
+
};
|
|
127
388
|
}
|
|
128
389
|
async get() {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
390
|
+
const result = await this.loader.load(this.model);
|
|
391
|
+
const data = this.options.locale ? result.content[this.options.locale] : result.content.en;
|
|
392
|
+
return this.executor.execute({
|
|
393
|
+
model: this.model,
|
|
394
|
+
data,
|
|
395
|
+
filters: this.filters,
|
|
396
|
+
includes: this.includes,
|
|
397
|
+
sorting: this.sorting,
|
|
398
|
+
pagination: this.pagination,
|
|
399
|
+
options: this.options
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
async first() {
|
|
403
|
+
const result = await this.limit(1).get();
|
|
404
|
+
return result.data[0] || null;
|
|
405
|
+
}
|
|
406
|
+
async count() {
|
|
407
|
+
const result = await this.get();
|
|
408
|
+
return result.total;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
__name(_ContentrainQueryBuilder, "ContentrainQueryBuilder");
|
|
412
|
+
var ContentrainQueryBuilder = _ContentrainQueryBuilder;
|
|
413
|
+
|
|
414
|
+
// src/query/executor.ts
|
|
415
|
+
var _QueryExecutor = class _QueryExecutor {
|
|
416
|
+
constructor(loader) {
|
|
417
|
+
this.loader = loader;
|
|
418
|
+
}
|
|
419
|
+
applyFilters(data, filters) {
|
|
420
|
+
return data.filter((item) => {
|
|
421
|
+
return filters.every((filter) => {
|
|
422
|
+
const value = item[filter.field];
|
|
423
|
+
const validOperators = ["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "contains", "startsWith", "endsWith"];
|
|
424
|
+
if (!validOperators.includes(filter.operator)) {
|
|
425
|
+
throw new Error(`Invalid operator: ${filter.operator}`);
|
|
426
|
+
}
|
|
427
|
+
switch (filter.operator) {
|
|
428
|
+
case "eq":
|
|
429
|
+
return value === filter.value;
|
|
430
|
+
case "ne":
|
|
431
|
+
return value !== filter.value;
|
|
432
|
+
case "gt":
|
|
433
|
+
return value > filter.value;
|
|
434
|
+
case "gte":
|
|
435
|
+
return value >= filter.value;
|
|
436
|
+
case "lt":
|
|
437
|
+
return value < filter.value;
|
|
438
|
+
case "lte":
|
|
439
|
+
return value <= filter.value;
|
|
440
|
+
case "in":
|
|
441
|
+
return Array.isArray(filter.value) && filter.value.includes(value);
|
|
442
|
+
case "nin":
|
|
443
|
+
return Array.isArray(filter.value) && !filter.value.includes(value);
|
|
444
|
+
case "contains":
|
|
445
|
+
return typeof value === "string" && value.includes(filter.value);
|
|
446
|
+
case "startsWith":
|
|
447
|
+
return typeof value === "string" && value.startsWith(filter.value);
|
|
448
|
+
case "endsWith":
|
|
449
|
+
return typeof value === "string" && value.endsWith(filter.value);
|
|
450
|
+
default:
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
applySorting(data, sorting) {
|
|
457
|
+
return [...data].sort((a, b) => {
|
|
458
|
+
for (const sort of sorting) {
|
|
459
|
+
const aValue = a[sort.field];
|
|
460
|
+
const bValue = b[sort.field];
|
|
461
|
+
if (aValue === bValue)
|
|
462
|
+
continue;
|
|
463
|
+
const direction = sort.direction === "asc" ? 1 : -1;
|
|
464
|
+
return aValue > bValue ? direction : -direction;
|
|
465
|
+
}
|
|
466
|
+
return 0;
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
applyPagination(data, limit, offset = 0) {
|
|
470
|
+
if (!limit)
|
|
471
|
+
return data.slice(offset);
|
|
472
|
+
return data.slice(offset, offset + limit);
|
|
473
|
+
}
|
|
474
|
+
async resolveIncludes(model, data, includes, options) {
|
|
475
|
+
const result = [...data];
|
|
476
|
+
for (const [field, config] of Object.entries(includes)) {
|
|
477
|
+
const relations = await this.loader.resolveRelation(
|
|
478
|
+
model,
|
|
479
|
+
field,
|
|
480
|
+
result,
|
|
481
|
+
options.locale
|
|
137
482
|
);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
483
|
+
if (config.include && relations.length) {
|
|
484
|
+
await this.resolveIncludes(
|
|
485
|
+
field,
|
|
486
|
+
relations,
|
|
487
|
+
config.include,
|
|
488
|
+
options
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
result.forEach((item) => {
|
|
492
|
+
const value = item[field];
|
|
493
|
+
const relatedItems = relations.filter((r) => {
|
|
494
|
+
if (Array.isArray(value)) {
|
|
495
|
+
return value.includes(r.ID);
|
|
145
496
|
}
|
|
497
|
+
return r.ID === value;
|
|
498
|
+
});
|
|
499
|
+
if (!item._relations) {
|
|
500
|
+
item._relations = {};
|
|
146
501
|
}
|
|
147
|
-
|
|
502
|
+
item._relations[field] = Array.isArray(value) ? relatedItems : relatedItems[0];
|
|
148
503
|
});
|
|
149
504
|
}
|
|
150
|
-
|
|
151
|
-
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
async execute({
|
|
508
|
+
model,
|
|
509
|
+
data,
|
|
510
|
+
filters = [],
|
|
511
|
+
includes = {},
|
|
512
|
+
sorting = [],
|
|
513
|
+
pagination = {},
|
|
514
|
+
options = {}
|
|
515
|
+
}) {
|
|
516
|
+
let result = [...data];
|
|
517
|
+
if (filters.length) {
|
|
518
|
+
result = this.applyFilters(result, filters);
|
|
152
519
|
}
|
|
153
|
-
if (
|
|
154
|
-
result =
|
|
520
|
+
if (Object.keys(includes).length) {
|
|
521
|
+
result = await this.resolveIncludes(model, result, includes, options);
|
|
155
522
|
}
|
|
156
|
-
|
|
523
|
+
if (sorting.length) {
|
|
524
|
+
result = this.applySorting(result, sorting);
|
|
525
|
+
}
|
|
526
|
+
const paginatedData = this.applyPagination(result, pagination.limit, pagination.offset);
|
|
527
|
+
return {
|
|
528
|
+
data: paginatedData,
|
|
529
|
+
total: result.length,
|
|
530
|
+
pagination: pagination.limit ? {
|
|
531
|
+
limit: pagination.limit,
|
|
532
|
+
offset: pagination.offset || 0,
|
|
533
|
+
hasMore: (pagination.offset || 0) + paginatedData.length < result.length
|
|
534
|
+
} : undefined
|
|
535
|
+
};
|
|
157
536
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
result[relationKey] = relatedData;
|
|
168
|
-
})
|
|
169
|
-
);
|
|
170
|
-
return result;
|
|
171
|
-
})
|
|
172
|
-
);
|
|
537
|
+
};
|
|
538
|
+
__name(_QueryExecutor, "QueryExecutor");
|
|
539
|
+
var QueryExecutor = _QueryExecutor;
|
|
540
|
+
|
|
541
|
+
// src/index.ts
|
|
542
|
+
var _ContentrainSDK = class _ContentrainSDK {
|
|
543
|
+
constructor(options) {
|
|
544
|
+
this.loader = new ContentLoader(options);
|
|
545
|
+
this.executor = new QueryExecutor(this.loader);
|
|
173
546
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
547
|
+
query(model) {
|
|
548
|
+
return new ContentrainQueryBuilder(
|
|
549
|
+
model,
|
|
550
|
+
this.executor,
|
|
551
|
+
this.loader
|
|
552
|
+
);
|
|
177
553
|
}
|
|
178
|
-
async
|
|
179
|
-
|
|
180
|
-
return items[0] ?? null;
|
|
554
|
+
async load(model) {
|
|
555
|
+
return this.loader.load(model);
|
|
181
556
|
}
|
|
182
557
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
558
|
+
__name(_ContentrainSDK, "ContentrainSDK");
|
|
559
|
+
var ContentrainSDK = _ContentrainSDK;
|
|
560
|
+
|
|
561
|
+
export { ContentLoader, ContentrainQueryBuilder, ContentrainSDK, MemoryCache, QueryExecutor };
|
|
562
|
+
//# sourceMappingURL=index.mjs.map
|
|
563
|
+
//# sourceMappingURL=index.mjs.map
|