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