@contentrain/query 2.0.1 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Contentrain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,134 +1,268 @@
1
1
  # @contentrain/query
2
2
 
3
- Query builder for Contentrain SDK.
3
+ Core package of the Contentrain SDK. This package provides the fundamental functionality and types for interacting with Contentrain CMS.
4
+
5
+ ## Features
6
+
7
+ - 🚀 High-performance content loading
8
+ - 💾 LRU caching with size-based eviction
9
+ - 🔍 Advanced query capabilities with type-safe operators
10
+ - 📦 Full TypeScript support with generic types
11
+ - 🛡️ Comprehensive error handling
12
+ - ⚡ Memory-optimized performance
13
+ - 🌍 Multi-language support
14
+ - 🔄 Relation resolution
4
15
 
5
16
  ## Installation
6
17
 
7
18
  ```bash
8
- npm install @contentrain/query @contentrain/core
19
+ # Using npm
20
+ npm install @contentrain/query
21
+
22
+ # Using yarn
23
+ yarn add @contentrain/query
24
+
25
+ # Using pnpm
26
+ pnpm add @contentrain/query
9
27
  ```
10
28
 
11
29
  ## Usage
12
30
 
31
+ ### Content Loading
32
+
13
33
  ```typescript
14
- import { ContentrainCore } from '@contentrain/core'
15
- import { ContentrainQuery } from '@contentrain/query'
34
+ import { ContentLoader } from '@contentrain/query';
35
+
36
+ const loader = new ContentLoader({
37
+ contentDir: './content',
38
+ defaultLocale: 'en',
39
+ cache: true,
40
+ ttl: 60 * 1000, // 1 minute
41
+ maxCacheSize: 100 // 100 MB
42
+ });
43
+
44
+ // Load all blog posts
45
+ const posts = await loader.load('posts');
46
+
47
+ // Load with locale
48
+ const trPosts = await loader.load('posts').locale('tr');
49
+
50
+ // Error handling
51
+ try {
52
+ const post = await loader.load('posts', 'non-existent-post');
53
+ } catch (error) {
54
+ if (error instanceof ContentNotFoundError) {
55
+ console.error('Post not found');
56
+ } else if (error instanceof ContentValidationError) {
57
+ console.error('Content validation failed');
58
+ }
59
+ }
60
+ ```
16
61
 
17
- // Initialize core and query
18
- const core = new ContentrainCore()
19
- const query = new ContentrainQuery(core)
62
+ ### Query Operations
20
63
 
21
- // Basic query
64
+ ```typescript
65
+ import { ContentrainSDK } from '@contentrain/query';
66
+
67
+ const sdk = new ContentrainSDK({
68
+ contentDir: './content'
69
+ });
70
+
71
+ // Type-safe querying
72
+ interface Post {
73
+ ID: string;
74
+ title: string;
75
+ status: 'draft' | 'published';
76
+ tags: string[];
77
+ createdAt: string;
78
+ }
79
+
80
+ const query = sdk.query<{
81
+ fields: Post;
82
+ locales: 'en' | 'tr';
83
+ relations: {
84
+ author: Author;
85
+ categories: Category[];
86
+ }
87
+ }>('posts');
88
+
89
+ // Available operators
22
90
  const posts = await query
23
- .from('posts')
24
- .where('status', 'publish')
25
- .get()
26
-
27
- // Complex query
28
- const featuredPosts = await query
29
- .from('posts')
30
- .where('status', 'publish')
31
- .where('featured', true)
91
+ .where('status', 'eq', 'published')
92
+ .where('tags', 'contains', ['javascript'])
93
+ .where('createdAt', 'gt', '2024-01-01')
94
+ .where('category', 'in', ['tech', 'programming'])
32
95
  .orderBy('createdAt', 'desc')
33
96
  .limit(5)
34
- .get()
35
-
36
- // Query with relations
37
- const postsWithAuthor = await query
38
- .from('posts')
39
- .with('author')
40
- .where('status', 'publish')
41
- .get()
42
-
43
- // Query with multiple conditions
44
- const searchPosts = await query
45
- .from('posts')
46
- .where([
47
- ['status', 'publish'],
48
- ['category', 'technology'],
49
- ['title', 'startsWith', 'How to']
50
- ])
51
- .get()
97
+ .get();
98
+
99
+ // Relation handling
100
+ const postsWithRelations = await query
101
+ .include(['author', 'categories'])
102
+ .where('status', 'eq', 'published')
103
+ .get();
104
+
105
+ // Locale support
106
+ const trPosts = await query
107
+ .locale('tr')
108
+ .where('status', 'eq', 'published')
109
+ .get();
52
110
  ```
53
111
 
54
- ## API Reference
55
-
56
- ### Constructor
112
+ ### Caching
57
113
 
58
114
  ```typescript
59
- constructor(core: ContentrainCore)
115
+ import { MemoryCache } from '@contentrain/query';
116
+
117
+ const cache = new MemoryCache({
118
+ maxSize: 100, // Maximum cache size in MB
119
+ defaultTTL: 60 * 1000, // Default TTL in ms
120
+ });
121
+
122
+ // Set with custom TTL
123
+ await cache.set('key', data, 5 * 60 * 1000); // 5 minutes TTL
124
+
125
+ // Get with type safety
126
+ const data = await cache.get<Post[]>('key');
127
+
128
+ // Cache stats
129
+ const stats = cache.getStats();
130
+ console.log(`
131
+ Hits: ${stats.hits}
132
+ Misses: ${stats.misses}
133
+ Size: ${stats.size} bytes
134
+ Last Cleanup: ${stats.lastCleanup}
135
+ `);
60
136
  ```
61
137
 
62
- Creates a new instance of ContentrainQuery.
63
-
64
- ### Methods
138
+ ## API Reference
65
139
 
66
- #### from
67
- ```typescript
68
- from(collection: string): ContentrainQuery
69
- ```
140
+ ### ContentrainSDK
70
141
 
71
- Specifies the collection to query.
142
+ Main entry point for the SDK.
72
143
 
73
- #### where
74
144
  ```typescript
75
- where(field: string, value: any): ContentrainQuery
76
- where(field: string, operator: FilterOperator, value: any): ContentrainQuery
77
- where(conditions: [string, any][] | [string, FilterOperator, any][]): ContentrainQuery
145
+ class ContentrainSDK {
146
+ constructor(options: ContentLoaderOptions)
147
+ query<T extends QueryConfig>(model: string): ContentrainQueryBuilder<T>
148
+ load<T>(model: string): Promise<LoaderResult<T>>
149
+ }
78
150
  ```
79
151
 
80
- Adds where conditions to the query.
152
+ ### Query Builder
81
153
 
82
- #### orderBy
83
154
  ```typescript
84
- orderBy(field: string, direction: 'asc' | 'desc' = 'asc'): ContentrainQuery
155
+ interface QueryBuilder<T> {
156
+ // Filter operations
157
+ where<K extends keyof T>(
158
+ field: K,
159
+ operator: QueryOperator,
160
+ value: T[K] | T[K][]
161
+ ): this
162
+
163
+ // Available operators:
164
+ // 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' |
165
+ // 'in' | 'nin' | 'contains' | 'startsWith' | 'endsWith'
166
+
167
+ // Relation operations
168
+ include(relations: string | string[]): this
169
+
170
+ // Sorting
171
+ orderBy(field: keyof T, direction?: 'asc' | 'desc'): this
172
+
173
+ // Pagination
174
+ limit(count: number): this
175
+ offset(count: number): this
176
+
177
+ // Locale
178
+ locale(code: string): this
179
+
180
+ // Cache control
181
+ cache(ttl?: number): this
182
+ noCache(): this
183
+ bypassCache(): this
184
+
185
+ // Execution
186
+ get(): Promise<QueryResult<T>>
187
+ first(): Promise<T | null>
188
+ count(): Promise<number>
189
+ }
190
+
191
+ interface QueryResult<T> {
192
+ data: T[]
193
+ total: number
194
+ pagination?: {
195
+ limit: number
196
+ offset: number
197
+ hasMore: boolean
198
+ }
199
+ }
85
200
  ```
86
201
 
87
- Orders the results by a field.
202
+ ### Cache Manager
88
203
 
89
- #### limit
90
204
  ```typescript
91
- limit(count: number): ContentrainQuery
205
+ interface CacheManager {
206
+ set<T>(key: string, value: T, ttl?: number): Promise<void>
207
+ get<T>(key: string): Promise<T | null>
208
+ delete(key: string): Promise<void>
209
+ clear(): Promise<void>
210
+ getStats(): CacheStats
211
+ }
212
+
213
+ interface CacheStats {
214
+ hits: number
215
+ misses: number
216
+ size: number
217
+ lastCleanup: number
218
+ }
92
219
  ```
93
220
 
94
- Limits the number of results.
221
+ ### Content Loader
95
222
 
96
- #### offset
97
223
  ```typescript
98
- offset(count: number): ContentrainQuery
224
+ interface ContentLoader {
225
+ load<T>(model: string): Promise<LoaderResult<T>>
226
+ resolveRelation<T, R>(
227
+ model: string,
228
+ relationField: keyof T,
229
+ data: T[],
230
+ locale?: string
231
+ ): Promise<R[]>
232
+ clearCache(): Promise<void>
233
+ refreshCache(model: string): Promise<void>
234
+ getCacheStats(): CacheStats
235
+ }
99
236
  ```
100
237
 
101
- Skips the specified number of results.
238
+ ## Error Handling
102
239
 
103
- #### with
104
- ```typescript
105
- with(...relations: string[]): ContentrainQuery
106
- ```
240
+ The package provides specific error types for different scenarios:
107
241
 
108
- Includes related content in the results.
109
-
110
- #### get
111
242
  ```typescript
112
- get<T>(): Promise<T[]>
243
+ import {
244
+ ContentrainError, // Base error class
245
+ ContentNotFoundError,
246
+ ContentValidationError,
247
+ CacheError,
248
+ RelationError
249
+ } from '@contentrain/query';
250
+
251
+ try {
252
+ const posts = await loader.load('posts');
253
+ } catch (error) {
254
+ if (error instanceof ContentNotFoundError) {
255
+ // Handle not found
256
+ } else if (error instanceof ContentValidationError) {
257
+ // Handle validation errors
258
+ } else if (error instanceof CacheError) {
259
+ // Handle cache errors
260
+ } else if (error instanceof RelationError) {
261
+ // Handle relation errors
262
+ }
263
+ }
113
264
  ```
114
265
 
115
- Executes the query and returns the results.
116
-
117
- ### Filter Operators
118
-
119
- - `equals` (default)
120
- - `notEquals`
121
- - `contains`
122
- - `notContains`
123
- - `startsWith`
124
- - `endsWith`
125
- - `exists`
126
- - `notExists`
127
- - `gt` (greater than)
128
- - `gte` (greater than or equal)
129
- - `lt` (less than)
130
- - `lte` (less than or equal)
131
-
132
266
  ## License
133
267
 
134
268
  MIT
package/dist/index.d.mts CHANGED
@@ -1,28 +1,286 @@
1
- import { IContentrainCore } from '@contentrain/core';
2
- import { ContentrainBaseModel, FilterCondition, SortDirection, WithRelation } from '@contentrain/types';
1
+ interface ContentrainConfig {
2
+ contentDir: string;
3
+ defaultLocale?: string;
4
+ models: {
5
+ [modelId: string]: {
6
+ localized: boolean;
7
+ defaultLocale?: string;
8
+ locales?: string[];
9
+ };
10
+ };
11
+ }
3
12
 
4
- declare class ContentrainQuery<T extends ContentrainBaseModel> {
5
- private core;
6
- private collection;
7
- private filters;
8
- private sorts;
13
+ interface BaseContentrainType {
14
+ ID: string;
15
+ createdAt: string;
16
+ updatedAt: string;
17
+ status: 'draft' | 'changed' | 'publish';
18
+ scheduled: boolean;
19
+ _relations?: {
20
+ [key: string]: BaseContentrainType | BaseContentrainType[];
21
+ };
22
+ }
23
+ type ContentrainStatus = 'draft' | 'changed' | 'publish';
24
+ interface ModelMetadata {
25
+ name: string;
26
+ modelId: string;
27
+ localization: boolean;
28
+ type: 'JSON';
29
+ createdBy: string;
30
+ isServerless: boolean;
31
+ }
32
+ interface FieldMetadata {
33
+ name: string;
34
+ fieldId: string;
35
+ modelId: string;
36
+ componentId: ContentrainComponentId;
37
+ fieldType: ContentrainFieldType;
38
+ options: FieldOptions;
39
+ validations: FieldValidations;
40
+ system?: boolean;
41
+ defaultField?: boolean;
42
+ }
43
+ type ContentrainFieldType = 'string' | 'number' | 'boolean' | 'array' | 'date' | 'media' | 'relation';
44
+ type ContentrainComponentId = 'single-line-text' | 'multi-line-text' | 'email' | 'url' | 'slug' | 'color' | 'json' | 'md-editor' | 'rich-text-editor' | 'integer' | 'decimal' | 'rating' | 'percent' | 'phone-number' | 'checkbox' | 'switch' | 'date' | 'date-time' | 'media' | 'one-to-one' | 'one-to-many';
45
+ interface FieldOptions {
46
+ 'title-field'?: {
47
+ value: boolean;
48
+ };
49
+ 'default-value'?: {
50
+ value: boolean;
51
+ form: {
52
+ [key: string]: {
53
+ value: any;
54
+ };
55
+ };
56
+ };
57
+ 'reference'?: {
58
+ value: boolean;
59
+ form: {
60
+ reference: {
61
+ value: string;
62
+ };
63
+ };
64
+ };
65
+ }
66
+ interface FieldValidations {
67
+ 'required-field'?: {
68
+ value: boolean;
69
+ };
70
+ 'unique-field'?: {
71
+ value: boolean;
72
+ };
73
+ 'input-range-field'?: {
74
+ value: {
75
+ min: number;
76
+ max: number;
77
+ };
78
+ };
79
+ }
80
+ type ContentrainLocales = string;
81
+
82
+ interface ContentLoaderOptions {
83
+ contentDir: string;
84
+ defaultLocale?: string;
85
+ cache?: boolean;
86
+ ttl?: number;
87
+ maxCacheSize?: number;
88
+ modelTTL?: {
89
+ [model: string]: number;
90
+ };
91
+ }
92
+ interface ModelConfig {
93
+ metadata: ModelMetadata;
94
+ fields: FieldMetadata[];
95
+ }
96
+ interface ContentFile<T extends BaseContentrainType = BaseContentrainType> {
97
+ model: string;
98
+ locale?: string;
99
+ data: T[];
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
+ }
115
+ interface LoaderResult<T extends BaseContentrainType = BaseContentrainType> {
116
+ model: ModelConfig;
117
+ content: {
118
+ [locale: string]: T[];
119
+ };
120
+ assets?: AssetMetadata[];
121
+ }
122
+ interface RelationConfig {
123
+ model: string;
124
+ type: 'one-to-one' | 'one-to-many';
125
+ foreignKey: string;
126
+ }
127
+ interface CacheStats {
128
+ hits: number;
129
+ misses: number;
130
+ size: number;
131
+ lastCleanup: number;
132
+ }
133
+ interface CacheEntry<T> {
134
+ data: T;
135
+ expireAt: number;
136
+ size: number;
137
+ createdAt: number;
138
+ }
139
+ interface MemoryCacheOptions {
140
+ maxSize?: number;
141
+ defaultTTL?: number;
142
+ }
143
+
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> {
149
+ field: string;
150
+ operator: Operator;
151
+ value: T extends Array<infer U> ? (ArrayOperator extends 'in' | 'nin' ? U[] : U) : T;
152
+ }
153
+ interface Sort {
154
+ field: string;
155
+ direction: 'asc' | 'desc';
156
+ }
157
+ interface Pagination {
158
+ limit?: number;
159
+ offset?: number;
160
+ }
161
+ interface Include {
162
+ [relation: string]: {
163
+ fields?: string[];
164
+ include?: Include;
165
+ };
166
+ }
167
+ interface QueryOptions {
168
+ locale?: string;
169
+ cache?: boolean;
170
+ ttl?: number;
171
+ }
172
+ interface QueryResult<T> {
173
+ data: T[];
174
+ total: number;
175
+ pagination?: {
176
+ limit: number;
177
+ offset: number;
178
+ hasMore: boolean;
179
+ };
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
+ }
186
+
187
+ declare class ContentLoader {
188
+ private options;
189
+ private modelConfigs;
9
190
  private relations;
10
- private limitCount?;
11
- private skipCount?;
12
- constructor(core: IContentrainCore | undefined, collection: string);
13
- where(field: keyof T, operator: FilterCondition<T>['operator'], value: T[keyof T]): this;
14
- sort(field: keyof T, direction?: SortDirection): this;
15
- take(limit: number): this;
16
- offset(skip: number): this;
17
- with(relation: keyof T): this;
18
- private getModelMetadata;
19
- private getRelatedData;
20
- private evaluateFilter;
21
- private evaluateSort;
22
- get(): Promise<T[]>;
23
- getWithRelations<K extends keyof T>(): Promise<WithRelation<T, K>[]>;
24
- first(): Promise<T | null>;
25
- firstWithRelations<K extends keyof T>(): Promise<WithRelation<T, K> | null>;
26
- }
27
-
28
- export { ContentrainQuery };
191
+ private cache;
192
+ constructor(options: ContentLoaderOptions);
193
+ private getCacheKey;
194
+ private getModelTTL;
195
+ clearCache(): Promise<void>;
196
+ refreshCache(model: string): Promise<void>;
197
+ getCacheStats(): CacheStats;
198
+ private loadModelConfig;
199
+ private loadContentFile;
200
+ private loadRelations;
201
+ private getModelLocales;
202
+ load<T extends BaseContentrainType>(model: string): Promise<LoaderResult<T>>;
203
+ resolveRelation<T extends BaseContentrainType, R extends BaseContentrainType>(model: string, relationField: keyof T, data: T[], locale?: string): Promise<R[]>;
204
+ }
205
+
206
+ declare class QueryExecutor {
207
+ private loader;
208
+ constructor(loader: ContentLoader);
209
+ private applyFilters;
210
+ private applySorting;
211
+ private applyPagination;
212
+ private resolveIncludes;
213
+ private applyStringOperation;
214
+ execute<T extends BaseContentrainType>({ model, data, filters, includes, sorting, pagination, options, }: {
215
+ model: string;
216
+ data: T[];
217
+ filters?: Filter[];
218
+ includes?: Include;
219
+ sorting?: Sort[];
220
+ pagination?: {
221
+ limit?: number;
222
+ offset?: number;
223
+ };
224
+ options?: QueryOptions;
225
+ }): Promise<QueryResult<T>>;
226
+ }
227
+
228
+ declare class ContentrainQueryBuilder<TFields extends BaseContentrainType, TLocales extends ContentrainLocales = 'en' | 'tr', TRelations extends Record<string, BaseContentrainType> = Record<string, never>> {
229
+ private model;
230
+ private filters;
231
+ private includes;
232
+ private sorting;
233
+ private pagination;
234
+ private options;
235
+ private executor;
236
+ private loader;
237
+ constructor(model: string, executor: QueryExecutor, loader: ContentLoader);
238
+ where<K extends keyof TFields, O extends Operator>(field: K, operator: O, value: O extends 'in' ? TFields[K][] : TFields[K]): this;
239
+ include<K extends keyof TRelations>(relation: K | K[]): this;
240
+ orderBy<K extends keyof TFields>(field: K, direction?: 'asc' | 'desc'): this;
241
+ limit(count: number): this;
242
+ offset(count: number): this;
243
+ locale(code: TLocales): this;
244
+ cache(ttl?: number): this;
245
+ noCache(): this;
246
+ bypassCache(): this;
247
+ toJSON(): {
248
+ model: string;
249
+ filters: Filter<any>[];
250
+ includes: Include;
251
+ sorting: Sort[];
252
+ pagination: Pagination;
253
+ options: QueryOptions;
254
+ };
255
+ get(): Promise<QueryResult<TFields>>;
256
+ first(): Promise<TFields | null>;
257
+ count(): Promise<number>;
258
+ }
259
+
260
+ declare class MemoryCache {
261
+ private cache;
262
+ private options;
263
+ private stats;
264
+ constructor(options?: MemoryCacheOptions);
265
+ private calculateSize;
266
+ set<T>(key: string, data: T, ttl?: number): Promise<void>;
267
+ private findOldestKey;
268
+ get<T>(key: string): Promise<T | null>;
269
+ delete(key: string): Promise<void>;
270
+ clear(): Promise<void>;
271
+ private cleanupCache;
272
+ getStats(): CacheStats;
273
+ }
274
+
275
+ declare class ContentrainSDK {
276
+ private loader;
277
+ private executor;
278
+ constructor(options: ContentLoaderOptions);
279
+ query<T extends QueryConfig<BaseContentrainType, string, Record<string, BaseContentrainType>>>(model: string): ContentrainQueryBuilder<T['fields'], T['locales'], T['relations']>;
280
+ load<T extends BaseContentrainType>(model: string): Promise<LoaderResult<T>>;
281
+ clearCache(): Promise<void>;
282
+ refreshCache(model: string): Promise<void>;
283
+ getCacheStats(): CacheStats;
284
+ }
285
+
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 };