@contentrain/query 3.0.0 → 3.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/index.d.mts CHANGED
@@ -1,8 +1,9 @@
1
1
  interface ContentrainConfig {
2
2
  contentDir: string;
3
+ defaultLocale?: string;
3
4
  models: {
4
5
  [modelId: string]: {
5
- localized?: boolean;
6
+ localized: boolean;
6
7
  defaultLocale?: string;
7
8
  locales?: string[];
8
9
  };
@@ -76,19 +77,7 @@ interface FieldValidations {
76
77
  };
77
78
  };
78
79
  }
79
- interface AssetMetadata {
80
- path: string;
81
- size: number;
82
- type: string;
83
- createdAt: string;
84
- updatedAt: string;
85
- }
86
80
  type ContentrainLocales = string;
87
- interface QueryConfig<TFields extends BaseContentrainType, TLocales extends ContentrainLocales, TRelations extends Record<string, BaseContentrainType>> {
88
- fields: TFields;
89
- locales: TLocales;
90
- relations: TRelations;
91
- }
92
81
 
93
82
  interface ContentLoaderOptions {
94
83
  contentDir: string;
@@ -109,11 +98,26 @@ interface ContentFile<T extends BaseContentrainType = BaseContentrainType> {
109
98
  locale?: string;
110
99
  data: T[];
111
100
  }
101
+ interface AssetMetadata {
102
+ path: string;
103
+ mimetype: string;
104
+ size: number;
105
+ alt: string;
106
+ meta: {
107
+ user: {
108
+ name: string;
109
+ email: string;
110
+ avatar: string;
111
+ };
112
+ createdAt: string;
113
+ };
114
+ }
112
115
  interface LoaderResult<T extends BaseContentrainType = BaseContentrainType> {
113
116
  model: ModelConfig;
114
117
  content: {
115
118
  [locale: string]: T[];
116
119
  };
120
+ assets?: AssetMetadata[];
117
121
  }
118
122
  interface RelationConfig {
119
123
  model: string;
@@ -132,12 +136,19 @@ interface CacheEntry<T> {
132
136
  size: number;
133
137
  createdAt: number;
134
138
  }
139
+ interface MemoryCacheOptions {
140
+ maxSize?: number;
141
+ defaultTTL?: number;
142
+ }
135
143
 
136
- type Operator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith';
137
- interface Filter {
144
+ type StringOperator = 'eq' | 'ne' | 'contains' | 'startsWith' | 'endsWith';
145
+ type NumericOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';
146
+ type ArrayOperator = 'in' | 'nin';
147
+ type Operator = StringOperator | NumericOperator | ArrayOperator;
148
+ interface Filter<T = any> {
138
149
  field: string;
139
150
  operator: Operator;
140
- value: any;
151
+ value: T extends Array<infer U> ? (ArrayOperator extends 'in' | 'nin' ? U[] : U) : T;
141
152
  }
142
153
  interface Sort {
143
154
  field: string;
@@ -167,6 +178,11 @@ interface QueryResult<T> {
167
178
  hasMore: boolean;
168
179
  };
169
180
  }
181
+ interface QueryConfig<TFields extends BaseContentrainType, TLocales extends ContentrainLocales = 'en' | 'tr', TRelations extends Record<string, BaseContentrainType> = Record<string, never>> {
182
+ fields: TFields;
183
+ locales: TLocales;
184
+ relations: TRelations;
185
+ }
170
186
 
171
187
  declare class ContentLoader {
172
188
  private options;
@@ -182,6 +198,7 @@ declare class ContentLoader {
182
198
  private loadModelConfig;
183
199
  private loadContentFile;
184
200
  private loadRelations;
201
+ private getModelLocales;
185
202
  load<T extends BaseContentrainType>(model: string): Promise<LoaderResult<T>>;
186
203
  resolveRelation<T extends BaseContentrainType, R extends BaseContentrainType>(model: string, relationField: keyof T, data: T[], locale?: string): Promise<R[]>;
187
204
  }
@@ -193,6 +210,7 @@ declare class QueryExecutor {
193
210
  private applySorting;
194
211
  private applyPagination;
195
212
  private resolveIncludes;
213
+ private applyStringOperation;
196
214
  execute<T extends BaseContentrainType>({ model, data, filters, includes, sorting, pagination, options, }: {
197
215
  model: string;
198
216
  data: T[];
@@ -228,13 +246,10 @@ declare class ContentrainQueryBuilder<TFields extends BaseContentrainType, TLoca
228
246
  bypassCache(): this;
229
247
  toJSON(): {
230
248
  model: string;
231
- filters: Filter[];
249
+ filters: Filter<any>[];
232
250
  includes: Include;
233
251
  sorting: Sort[];
234
- pagination: {
235
- limit?: number;
236
- offset?: number;
237
- };
252
+ pagination: Pagination;
238
253
  options: QueryOptions;
239
254
  };
240
255
  get(): Promise<QueryResult<TFields>>;
@@ -242,10 +257,6 @@ declare class ContentrainQueryBuilder<TFields extends BaseContentrainType, TLoca
242
257
  count(): Promise<number>;
243
258
  }
244
259
 
245
- interface MemoryCacheOptions {
246
- maxSize?: number;
247
- defaultTTL?: number;
248
- }
249
260
  declare class MemoryCache {
250
261
  private cache;
251
262
  private options;
@@ -267,6 +278,9 @@ declare class ContentrainSDK {
267
278
  constructor(options: ContentLoaderOptions);
268
279
  query<T extends QueryConfig<BaseContentrainType, string, Record<string, BaseContentrainType>>>(model: string): ContentrainQueryBuilder<T['fields'], T['locales'], T['relations']>;
269
280
  load<T extends BaseContentrainType>(model: string): Promise<LoaderResult<T>>;
281
+ clearCache(): Promise<void>;
282
+ refreshCache(model: string): Promise<void>;
283
+ getCacheStats(): CacheStats;
270
284
  }
271
285
 
272
- export { type AssetMetadata, type BaseContentrainType, type CacheEntry, type CacheStats, type ContentFile, ContentLoader, type ContentLoaderOptions, type ContentrainComponentId, type ContentrainConfig, type ContentrainFieldType, type ContentrainLocales, ContentrainQueryBuilder, ContentrainSDK, type ContentrainStatus, type FieldMetadata, type FieldOptions, type FieldValidations, type Filter, type Include, type LoaderResult, MemoryCache, type MemoryCacheOptions, type ModelConfig, type ModelMetadata, type Operator, type Pagination, type QueryConfig, QueryExecutor, type QueryOptions, type QueryResult, type RelationConfig, type Sort };
286
+ export { type ArrayOperator, type AssetMetadata, type BaseContentrainType, type CacheEntry, type CacheStats, type ContentFile, ContentLoader, type ContentLoaderOptions, type ContentrainComponentId, type ContentrainConfig, type ContentrainFieldType, type ContentrainLocales, ContentrainQueryBuilder, ContentrainSDK, type ContentrainStatus, type FieldMetadata, type FieldOptions, type FieldValidations, type Filter, type Include, type LoaderResult, MemoryCache, type MemoryCacheOptions, type ModelConfig, type ModelMetadata, type NumericOperator, type Operator, type Pagination, type QueryConfig, QueryExecutor, type QueryOptions, type QueryResult, type RelationConfig, type Sort, type StringOperator };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  interface ContentrainConfig {
2
2
  contentDir: string;
3
+ defaultLocale?: string;
3
4
  models: {
4
5
  [modelId: string]: {
5
- localized?: boolean;
6
+ localized: boolean;
6
7
  defaultLocale?: string;
7
8
  locales?: string[];
8
9
  };
@@ -76,19 +77,7 @@ interface FieldValidations {
76
77
  };
77
78
  };
78
79
  }
79
- interface AssetMetadata {
80
- path: string;
81
- size: number;
82
- type: string;
83
- createdAt: string;
84
- updatedAt: string;
85
- }
86
80
  type ContentrainLocales = string;
87
- interface QueryConfig<TFields extends BaseContentrainType, TLocales extends ContentrainLocales, TRelations extends Record<string, BaseContentrainType>> {
88
- fields: TFields;
89
- locales: TLocales;
90
- relations: TRelations;
91
- }
92
81
 
93
82
  interface ContentLoaderOptions {
94
83
  contentDir: string;
@@ -109,11 +98,26 @@ interface ContentFile<T extends BaseContentrainType = BaseContentrainType> {
109
98
  locale?: string;
110
99
  data: T[];
111
100
  }
101
+ interface AssetMetadata {
102
+ path: string;
103
+ mimetype: string;
104
+ size: number;
105
+ alt: string;
106
+ meta: {
107
+ user: {
108
+ name: string;
109
+ email: string;
110
+ avatar: string;
111
+ };
112
+ createdAt: string;
113
+ };
114
+ }
112
115
  interface LoaderResult<T extends BaseContentrainType = BaseContentrainType> {
113
116
  model: ModelConfig;
114
117
  content: {
115
118
  [locale: string]: T[];
116
119
  };
120
+ assets?: AssetMetadata[];
117
121
  }
118
122
  interface RelationConfig {
119
123
  model: string;
@@ -132,12 +136,19 @@ interface CacheEntry<T> {
132
136
  size: number;
133
137
  createdAt: number;
134
138
  }
139
+ interface MemoryCacheOptions {
140
+ maxSize?: number;
141
+ defaultTTL?: number;
142
+ }
135
143
 
136
- type Operator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith';
137
- interface Filter {
144
+ type StringOperator = 'eq' | 'ne' | 'contains' | 'startsWith' | 'endsWith';
145
+ type NumericOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';
146
+ type ArrayOperator = 'in' | 'nin';
147
+ type Operator = StringOperator | NumericOperator | ArrayOperator;
148
+ interface Filter<T = any> {
138
149
  field: string;
139
150
  operator: Operator;
140
- value: any;
151
+ value: T extends Array<infer U> ? (ArrayOperator extends 'in' | 'nin' ? U[] : U) : T;
141
152
  }
142
153
  interface Sort {
143
154
  field: string;
@@ -167,6 +178,11 @@ interface QueryResult<T> {
167
178
  hasMore: boolean;
168
179
  };
169
180
  }
181
+ interface QueryConfig<TFields extends BaseContentrainType, TLocales extends ContentrainLocales = 'en' | 'tr', TRelations extends Record<string, BaseContentrainType> = Record<string, never>> {
182
+ fields: TFields;
183
+ locales: TLocales;
184
+ relations: TRelations;
185
+ }
170
186
 
171
187
  declare class ContentLoader {
172
188
  private options;
@@ -182,6 +198,7 @@ declare class ContentLoader {
182
198
  private loadModelConfig;
183
199
  private loadContentFile;
184
200
  private loadRelations;
201
+ private getModelLocales;
185
202
  load<T extends BaseContentrainType>(model: string): Promise<LoaderResult<T>>;
186
203
  resolveRelation<T extends BaseContentrainType, R extends BaseContentrainType>(model: string, relationField: keyof T, data: T[], locale?: string): Promise<R[]>;
187
204
  }
@@ -193,6 +210,7 @@ declare class QueryExecutor {
193
210
  private applySorting;
194
211
  private applyPagination;
195
212
  private resolveIncludes;
213
+ private applyStringOperation;
196
214
  execute<T extends BaseContentrainType>({ model, data, filters, includes, sorting, pagination, options, }: {
197
215
  model: string;
198
216
  data: T[];
@@ -228,13 +246,10 @@ declare class ContentrainQueryBuilder<TFields extends BaseContentrainType, TLoca
228
246
  bypassCache(): this;
229
247
  toJSON(): {
230
248
  model: string;
231
- filters: Filter[];
249
+ filters: Filter<any>[];
232
250
  includes: Include;
233
251
  sorting: Sort[];
234
- pagination: {
235
- limit?: number;
236
- offset?: number;
237
- };
252
+ pagination: Pagination;
238
253
  options: QueryOptions;
239
254
  };
240
255
  get(): Promise<QueryResult<TFields>>;
@@ -242,10 +257,6 @@ declare class ContentrainQueryBuilder<TFields extends BaseContentrainType, TLoca
242
257
  count(): Promise<number>;
243
258
  }
244
259
 
245
- interface MemoryCacheOptions {
246
- maxSize?: number;
247
- defaultTTL?: number;
248
- }
249
260
  declare class MemoryCache {
250
261
  private cache;
251
262
  private options;
@@ -267,6 +278,9 @@ declare class ContentrainSDK {
267
278
  constructor(options: ContentLoaderOptions);
268
279
  query<T extends QueryConfig<BaseContentrainType, string, Record<string, BaseContentrainType>>>(model: string): ContentrainQueryBuilder<T['fields'], T['locales'], T['relations']>;
269
280
  load<T extends BaseContentrainType>(model: string): Promise<LoaderResult<T>>;
281
+ clearCache(): Promise<void>;
282
+ refreshCache(model: string): Promise<void>;
283
+ getCacheStats(): CacheStats;
270
284
  }
271
285
 
272
- export { type AssetMetadata, type BaseContentrainType, type CacheEntry, type CacheStats, type ContentFile, ContentLoader, type ContentLoaderOptions, type ContentrainComponentId, type ContentrainConfig, type ContentrainFieldType, type ContentrainLocales, ContentrainQueryBuilder, ContentrainSDK, type ContentrainStatus, type FieldMetadata, type FieldOptions, type FieldValidations, type Filter, type Include, type LoaderResult, MemoryCache, type MemoryCacheOptions, type ModelConfig, type ModelMetadata, type Operator, type Pagination, type QueryConfig, QueryExecutor, type QueryOptions, type QueryResult, type RelationConfig, type Sort };
286
+ export { type ArrayOperator, type AssetMetadata, type BaseContentrainType, type CacheEntry, type CacheStats, type ContentFile, ContentLoader, type ContentLoaderOptions, type ContentrainComponentId, type ContentrainConfig, type ContentrainFieldType, type ContentrainLocales, ContentrainQueryBuilder, ContentrainSDK, type ContentrainStatus, type FieldMetadata, type FieldOptions, type FieldValidations, type Filter, type Include, type LoaderResult, MemoryCache, type MemoryCacheOptions, type ModelConfig, type ModelMetadata, type NumericOperator, type Operator, type Pagination, type QueryConfig, QueryExecutor, type QueryOptions, type QueryResult, type RelationConfig, type Sort, type StringOperator };
package/dist/index.js CHANGED
@@ -174,14 +174,6 @@ var _ContentLoader = class _ContentLoader {
174
174
  const modelPath = path.join(this.options.contentDir, "models", `${model}.json`);
175
175
  const modelContent = await promises.readFile(modelPath, "utf-8");
176
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
177
  return {
186
178
  metadata: modelMetadata,
187
179
  fields: modelFields
@@ -190,12 +182,22 @@ var _ContentLoader = class _ContentLoader {
190
182
  throw new Error(`Failed to load model config for ${model}: ${error?.message || "Unknown error"}`);
191
183
  }
192
184
  }
193
- async loadContentFile(model, locale) {
185
+ async loadContentFile(model, locale = "default") {
194
186
  try {
187
+ const modelConfig = await this.loadModelConfig(model);
195
188
  let contentPath;
196
- if (locale) {
189
+ if (modelConfig.metadata.localization) {
190
+ if (!locale || locale === "default") {
191
+ if (!this.options.defaultLocale) {
192
+ throw new Error(`Default locale is required for localized model "${model}"`);
193
+ }
194
+ locale = this.options.defaultLocale;
195
+ }
197
196
  contentPath = path.join(this.options.contentDir, model, `${locale}.json`);
198
197
  } else {
198
+ if (locale !== "default") {
199
+ console.warn(`Locale "${locale}" specified for non-localized model "${model}". This parameter will be ignored.`);
200
+ }
199
201
  contentPath = path.join(this.options.contentDir, model, `${model}.json`);
200
202
  }
201
203
  const content = await promises.readFile(contentPath, "utf-8");
@@ -203,7 +205,7 @@ var _ContentLoader = class _ContentLoader {
203
205
  const data = JSON.parse(content);
204
206
  return {
205
207
  model,
206
- locale,
208
+ locale: modelConfig.metadata.localization ? locale : void 0,
207
209
  data
208
210
  };
209
211
  } catch {
@@ -243,8 +245,31 @@ var _ContentLoader = class _ContentLoader {
243
245
  throw new Error(`Failed to load relations for ${model}: ${error?.message || "Unknown error"}`);
244
246
  }
245
247
  }
248
+ async getModelLocales(model, modelConfig) {
249
+ try {
250
+ if (!modelConfig.metadata.localization) {
251
+ return ["default"];
252
+ }
253
+ const modelDir = path.join(this.options.contentDir, model);
254
+ const files = await promises.readdir(modelDir);
255
+ const locales = files.filter((file) => file.endsWith(".json")).map((file) => file.replace(".json", "")).filter((locale) => locale !== model);
256
+ if (locales.length === 0) {
257
+ if (!this.options.defaultLocale) {
258
+ throw new Error(`No locale files found for localized model "${model}" and no default locale specified`);
259
+ }
260
+ return [this.options.defaultLocale];
261
+ }
262
+ return locales;
263
+ } catch (error) {
264
+ if (!this.options.defaultLocale) {
265
+ throw new Error(`Failed to read locales for model ${model} and no default locale specified: ${error?.message}`);
266
+ }
267
+ console.warn(`Failed to read locales for model ${model}: ${error?.message}`);
268
+ return [this.options.defaultLocale];
269
+ }
270
+ }
246
271
  async load(model) {
247
- const cacheKey = this.getCacheKey(model);
272
+ const cacheKey = `${model}`;
248
273
  if (this.options.cache) {
249
274
  const cached = await this.cache.get(cacheKey);
250
275
  if (cached)
@@ -256,18 +281,34 @@ var _ContentLoader = class _ContentLoader {
256
281
  this.relations.set(model, relations);
257
282
  const content = {};
258
283
  if (modelConfig.metadata.localization) {
259
- const locales = ["en", "tr"];
284
+ const locales = await this.getModelLocales(model, modelConfig);
260
285
  for (const locale of locales) {
261
- const file = await this.loadContentFile(model, locale);
262
- content[locale] = file.data;
286
+ try {
287
+ const file = await this.loadContentFile(model, locale);
288
+ content[locale] = file.data;
289
+ } catch (error) {
290
+ console.warn(`Failed to load content for locale ${locale}: ${error?.message}`);
291
+ if (locale === this.options.defaultLocale) {
292
+ throw error;
293
+ }
294
+ }
263
295
  }
264
296
  } else {
265
297
  const file = await this.loadContentFile(model);
266
298
  content.default = file.data;
267
299
  }
300
+ let assets;
301
+ try {
302
+ const assetsPath = path.join(this.options.contentDir, "assets.json");
303
+ const assetsContent = await promises.readFile(assetsPath, "utf-8");
304
+ assets = JSON.parse(assetsContent);
305
+ } catch (error) {
306
+ console.warn("Assets file not found or cannot be read:", error);
307
+ }
268
308
  const result = {
269
309
  model: modelConfig,
270
- content
310
+ content,
311
+ assets
271
312
  };
272
313
  if (this.options.cache) {
273
314
  const ttl = this.getModelTTL(model);
@@ -297,14 +338,16 @@ var _ContentLoader = class _ContentLoader {
297
338
  return relatedItem;
298
339
  });
299
340
  } 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
- });
341
+ const uniqueIds = new Set(
342
+ data.flatMap(
343
+ (item) => Array.isArray(item[relationField]) ? item[relationField] : [item[relationField]]
344
+ )
345
+ );
346
+ const items = Array.from(uniqueIds).map((id) => relatedData.find((r) => r.ID === id)).filter(Boolean);
347
+ if (items.length !== uniqueIds.size) {
348
+ throw new Error("Failed to resolve relation: Some related items not found");
349
+ }
350
+ return items;
308
351
  }
309
352
  } catch (error) {
310
353
  throw new Error(`Failed to resolve relation: ${error.message}`);
@@ -390,7 +433,20 @@ var _ContentrainQueryBuilder = class _ContentrainQueryBuilder {
390
433
  }
391
434
  async get() {
392
435
  const result = await this.loader.load(this.model);
393
- const data = this.options.locale ? result.content[this.options.locale] : result.content.en;
436
+ const modelConfig = result.model;
437
+ let data;
438
+ if (modelConfig.metadata.localization) {
439
+ const locale = this.options.locale || "en";
440
+ data = result.content[locale];
441
+ if (!data) {
442
+ throw new Error(`Content not found for locale: ${locale}`);
443
+ }
444
+ } else {
445
+ if (!result.content.default) {
446
+ throw new Error(`Content not found for model: ${this.model}`);
447
+ }
448
+ data = result.content.default;
449
+ }
394
450
  return this.executor.execute({
395
451
  model: this.model,
396
452
  data,
@@ -420,50 +476,67 @@ var _QueryExecutor = class _QueryExecutor {
420
476
  }
421
477
  applyFilters(data, filters) {
422
478
  return data.filter((item) => {
423
- return filters.every((filter) => {
424
- const value = item[filter.field];
479
+ return filters.every(({ field, operator, value }) => {
480
+ const itemValue = item[field];
425
481
  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}`);
482
+ if (!validOperators.includes(operator)) {
483
+ throw new Error(`Invalid operator: ${operator}`);
484
+ }
485
+ if (typeof itemValue === "string" && typeof value === "string") {
486
+ return this.applyStringOperation(itemValue, operator, value);
487
+ }
488
+ if (Array.isArray(value)) {
489
+ switch (operator) {
490
+ case "in":
491
+ return value.includes(itemValue);
492
+ case "nin":
493
+ return !value.includes(itemValue);
494
+ default:
495
+ throw new Error(`Invalid array operator: ${operator}`);
496
+ }
497
+ }
498
+ if (Array.isArray(itemValue)) {
499
+ switch (operator) {
500
+ case "in":
501
+ return value.some((v) => itemValue.includes(v));
502
+ case "nin":
503
+ return !value.some((v) => itemValue.includes(v));
504
+ default:
505
+ throw new Error(`Invalid array operator: ${operator}`);
506
+ }
428
507
  }
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;
508
+ if (typeof itemValue === "number" && typeof value === "number") {
509
+ switch (operator) {
510
+ case "eq":
511
+ return itemValue === value;
512
+ case "ne":
513
+ return itemValue !== value;
514
+ case "gt":
515
+ return itemValue > value;
516
+ case "gte":
517
+ return itemValue >= value;
518
+ case "lt":
519
+ return itemValue < value;
520
+ case "lte":
521
+ return itemValue <= value;
522
+ }
454
523
  }
524
+ return false;
455
525
  });
456
526
  });
457
527
  }
458
528
  applySorting(data, sorting) {
459
529
  return [...data].sort((a, b) => {
460
- for (const sort of sorting) {
461
- const aValue = a[sort.field];
462
- const bValue = b[sort.field];
530
+ for (const { field, direction } of sorting) {
531
+ if (!(field in a)) {
532
+ throw new Error(`Invalid sort field: ${field}`);
533
+ }
534
+ const aValue = a[field];
535
+ const bValue = b[field];
463
536
  if (aValue === bValue)
464
537
  continue;
465
- const direction = sort.direction === "asc" ? 1 : -1;
466
- return aValue > bValue ? direction : -direction;
538
+ const compareResult = aValue < bValue ? -1 : 1;
539
+ return direction === "asc" ? compareResult : -compareResult;
467
540
  }
468
541
  return 0;
469
542
  });
@@ -506,6 +579,24 @@ var _QueryExecutor = class _QueryExecutor {
506
579
  }
507
580
  return result;
508
581
  }
582
+ applyStringOperation(value, operator, searchValue) {
583
+ switch (operator) {
584
+ case "eq":
585
+ return value === searchValue;
586
+ case "ne":
587
+ return value !== searchValue;
588
+ case "contains":
589
+ return value.toLowerCase().includes(searchValue.toLowerCase());
590
+ case "startsWith":
591
+ return value.toLowerCase().startsWith(searchValue.toLowerCase());
592
+ case "endsWith":
593
+ return value.toLowerCase().endsWith(searchValue.toLowerCase());
594
+ default: {
595
+ const _exhaustiveCheck = operator;
596
+ return _exhaustiveCheck;
597
+ }
598
+ }
599
+ }
509
600
  async execute({
510
601
  model,
511
602
  data,
@@ -556,6 +647,15 @@ var _ContentrainSDK = class _ContentrainSDK {
556
647
  async load(model) {
557
648
  return this.loader.load(model);
558
649
  }
650
+ async clearCache() {
651
+ return this.loader.clearCache();
652
+ }
653
+ async refreshCache(model) {
654
+ return this.loader.refreshCache(model);
655
+ }
656
+ getCacheStats() {
657
+ return this.loader.getCacheStats();
658
+ }
559
659
  };
560
660
  __name(_ContentrainSDK, "ContentrainSDK");
561
661
  var ContentrainSDK = _ContentrainSDK;