@contentrain/query 3.2.0 → 5.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 CHANGED
@@ -1,21 +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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 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,268 +1,246 @@
1
- # @contentrain/query
2
-
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
15
-
16
- ## Installation
17
-
18
- ```bash
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
27
- ```
28
-
29
- ## Usage
30
-
31
- ### Content Loading
32
-
33
- ```typescript
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
- ```
61
-
62
- ### Query Operations
63
-
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
90
- const posts = await query
91
- .where('status', 'eq', 'published')
92
- .where('tags', 'contains', ['javascript'])
93
- .where('createdAt', 'gt', '2024-01-01')
94
- .where('category', 'in', ['tech', 'programming'])
95
- .orderBy('createdAt', 'desc')
96
- .limit(5)
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();
110
- ```
111
-
112
- ### Caching
113
-
114
- ```typescript
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
- `);
136
- ```
137
-
138
- ## API Reference
139
-
140
- ### ContentrainSDK
141
-
142
- Main entry point for the SDK.
143
-
144
- ```typescript
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
- }
150
- ```
151
-
152
- ### Query Builder
153
-
154
- ```typescript
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
- }
200
- ```
201
-
202
- ### Cache Manager
203
-
204
- ```typescript
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
- }
219
- ```
220
-
221
- ### Content Loader
222
-
223
- ```typescript
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
- }
236
- ```
237
-
238
- ## Error Handling
239
-
240
- The package provides specific error types for different scenarios:
241
-
242
- ```typescript
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
- }
264
- ```
265
-
266
- ## License
267
-
268
- MIT
1
+ # `@contentrain/query`
2
+
3
+ Type-safe generated query SDK for Contentrain.
4
+
5
+ Contentrain stores content in a git-backed `.contentrain/` directory. This package turns that content model into a generated JS/TS client with:
6
+
7
+ - exact TypeScript types from your models
8
+ - zero-dependency query runtime
9
+ - Node `#contentrain` subpath imports
10
+ - ESM and CommonJS output
11
+ - framework-agnostic usage across app and server environments
12
+
13
+ ## 🚀 Install
14
+
15
+ ```bash
16
+ pnpm add @contentrain/query
17
+ ```
18
+
19
+ Requirements:
20
+
21
+ - Node.js `22+`
22
+ - a Contentrain project with `.contentrain/config.json`
23
+
24
+ ## ✨ What This Package Provides
25
+
26
+ `@contentrain/query` has two roles:
27
+
28
+ 1. **Generator**
29
+ - reads `.contentrain/config.json`, models, and content files
30
+ - writes `.contentrain/client/`
31
+ - injects `#contentrain` imports into your `package.json`
32
+
33
+ 2. **Base runtime**
34
+ - exports low-level runtime classes for framework SDK authors
35
+ - exports `createContentrainClient(projectRoot?)` for loading a generated client module
36
+
37
+ ## 🚀 Quick Start
38
+
39
+ Generate a client:
40
+
41
+ ```bash
42
+ npx contentrain-query generate
43
+ ```
44
+
45
+ This writes:
46
+
47
+ ```text
48
+ .contentrain/client/
49
+ index.mjs
50
+ index.cjs
51
+ index.d.ts
52
+ data/*
53
+ ```
54
+
55
+ It also updates your `package.json` with:
56
+
57
+ ```json
58
+ {
59
+ "imports": {
60
+ "#contentrain": {
61
+ "types": "./.contentrain/client/index.d.ts",
62
+ "import": "./.contentrain/client/index.mjs",
63
+ "require": "./.contentrain/client/index.cjs",
64
+ "default": "./.contentrain/client/index.mjs"
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ Then use the generated client in your app:
71
+
72
+ ```ts
73
+ import { query, singleton, dictionary, document } from '#contentrain'
74
+
75
+ const posts = query('blog-post')
76
+ .locale('en')
77
+ .where('status', 'published')
78
+ .sort('title')
79
+ .all()
80
+
81
+ const hero = singleton('hero').locale('en').get()
82
+ const messages = dictionary('error-messages').locale('en').get()
83
+ const article = document('blog-article').locale('en').bySlug('welcome-post')
84
+ ```
85
+
86
+ ## 📦 Generated Client API
87
+
88
+ The generated client exposes four entry points:
89
+
90
+ ### `query(model)`
91
+
92
+ For collection models.
93
+
94
+ Supported methods:
95
+
96
+ - `locale(lang)`
97
+ - `where(field, value)`
98
+ - `sort(field, order?)`
99
+ - `limit(n)`
100
+ - `offset(n)`
101
+ - `include(...fields)`
102
+ - `first()`
103
+ - `all()`
104
+
105
+ ### `singleton(model)`
106
+
107
+ For singleton models.
108
+
109
+ Supported methods:
110
+
111
+ - `locale(lang)`
112
+ - `include(...fields)`
113
+ - `get()`
114
+
115
+ ### `dictionary(model)`
116
+
117
+ For dictionary models.
118
+
119
+ Supported methods:
120
+
121
+ - `locale(lang)`
122
+ - `get()`
123
+ - `get(key)`
124
+
125
+ ### `document(model)`
126
+
127
+ For markdown/document models.
128
+
129
+ Supported methods:
130
+
131
+ - `locale(lang)`
132
+ - `where(field, value)`
133
+ - `include(...fields)`
134
+ - `bySlug(slug)`
135
+ - `first()`
136
+ - `all()`
137
+
138
+ ## 🔗 Relations
139
+
140
+ Generated clients support relation resolution via `include(...)`.
141
+
142
+ Examples:
143
+
144
+ ```ts
145
+ const posts = query('blog-post')
146
+ .locale('en')
147
+ .include('author', 'tags')
148
+ .all()
149
+
150
+ const settings = singleton('site-settings')
151
+ .locale('en')
152
+ .include('featured_post')
153
+ .get()
154
+ ```
155
+
156
+ ## 🧱 Framework SDK Authors
157
+
158
+ The package root exports runtime primitives and an async loader:
159
+
160
+ ```ts
161
+ import { createContentrainClient } from '@contentrain/query'
162
+
163
+ const client = await createContentrainClient(process.cwd())
164
+ const posts = client.query('blog-post').locale('en').all()
165
+ ```
166
+
167
+ Public root exports:
168
+
169
+ - `QueryBuilder`
170
+ - `SingletonAccessor`
171
+ - `DictionaryAccessor`
172
+ - `DocumentQuery`
173
+ - `createContentrainClient`
174
+
175
+ ## 🧩 CommonJS Usage
176
+
177
+ Generated clients support CommonJS through `init()`:
178
+
179
+ ```js
180
+ const clientModule = require('#contentrain')
181
+ const client = await clientModule.init()
182
+
183
+ const hero = client.singleton('hero').get()
184
+ ```
185
+
186
+ ## 🛠 CLI
187
+
188
+ Generate once:
189
+
190
+ ```bash
191
+ npx contentrain-query generate
192
+ ```
193
+
194
+ Generate in watch mode:
195
+
196
+ ```bash
197
+ npx contentrain-query generate --watch
198
+ ```
199
+
200
+ Use a different project root:
201
+
202
+ ```bash
203
+ npx contentrain-query generate --root /path/to/project
204
+ ```
205
+
206
+ ## 📤 Package Exports
207
+
208
+ Main package:
209
+
210
+ - `@contentrain/query`
211
+
212
+ Generator entry:
213
+
214
+ - `@contentrain/query/generate`
215
+
216
+ ## 🧠 Design Constraints
217
+
218
+ This package intentionally:
219
+
220
+ - generates into `.contentrain/client/` instead of `node_modules`
221
+ - uses `package.json#imports` instead of custom alias plugins
222
+ - ships zero-dependency runtime classes
223
+ - keeps the runtime framework-agnostic
224
+ - treats generated client output as the primary consumer surface
225
+
226
+ ## 🛠 Development
227
+
228
+ From the monorepo root:
229
+
230
+ ```bash
231
+ pnpm --filter @contentrain/query build
232
+ pnpm --filter @contentrain/query test
233
+ pnpm --filter @contentrain/query typecheck
234
+ pnpm exec oxlint packages/sdk/js/src packages/sdk/js/tests
235
+ ```
236
+
237
+ ## 🔗 Related Packages
238
+
239
+ - `contentrain` — CLI that runs project initialization, validation, serve, and generation flows
240
+ - `@contentrain/mcp` local-first MCP server and core content workflow engine
241
+ - `@contentrain/types` — shared schema and model definitions
242
+ - `@contentrain/rules` — agent rules and prompts
243
+
244
+ ## 📄 License
245
+
246
+ MIT
package/dist/cli.cjs ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ const require_generate = require("./generate-CPKYh6ZU.cjs");
3
+ let node_path = require("node:path");
4
+ let node_fs = require("node:fs");
5
+ //#region src/cli.ts
6
+ async function main() {
7
+ const args = process.argv.slice(2);
8
+ if (args[0] !== "generate" && args.length === 0) {
9
+ console.log("Usage: contentrain-query generate [--root <path>] [--watch]");
10
+ console.log("");
11
+ console.log("Commands:");
12
+ console.log(" generate Generate typed client from .contentrain/ project files");
13
+ console.log("");
14
+ console.log("Options:");
15
+ console.log(" --root Project root directory (default: cwd)");
16
+ console.log(" --watch Watch for changes and regenerate automatically");
17
+ process.exit(0);
18
+ }
19
+ const command = args[0] === "generate" ? "generate" : args[0];
20
+ if (command !== "generate") {
21
+ console.error(`Unknown command: ${command}`);
22
+ process.exit(1);
23
+ }
24
+ const rootIdx = args.indexOf("--root");
25
+ const rootArg = rootIdx !== -1 ? args[rootIdx + 1] : void 0;
26
+ const projectRoot = rootArg ? (0, node_path.resolve)(rootArg) : process.cwd();
27
+ const watchMode = args.includes("--watch");
28
+ try {
29
+ const result = await require_generate.generate({ projectRoot });
30
+ console.log(`@contentrain/query — generated client`);
31
+ console.log(` Models: ${result.typesCount}`);
32
+ console.log(` Data modules: ${result.dataModulesCount}`);
33
+ console.log(` Files: ${result.generatedFiles.length}`);
34
+ if (result.packageJsonUpdated) console.log(` package.json: #contentrain imports added`);
35
+ console.log(` Output: .contentrain/client/`);
36
+ if (watchMode) startWatch(projectRoot);
37
+ } catch (err) {
38
+ console.error("Generate failed:", err.message);
39
+ process.exit(1);
40
+ }
41
+ }
42
+ function startWatch(projectRoot) {
43
+ const crDir = (0, node_path.join)(projectRoot, ".contentrain");
44
+ const modelsDir = (0, node_path.join)(crDir, "models");
45
+ const contentDir = (0, node_path.join)(crDir, "content");
46
+ const configFile = (0, node_path.join)(crDir, "config.json");
47
+ let debounceTimer = null;
48
+ const regenerate = async () => {
49
+ try {
50
+ const result = await require_generate.generate({ projectRoot });
51
+ console.log(` Regenerated: ${result.typesCount} models, ${result.dataModulesCount} data modules`);
52
+ } catch (err) {
53
+ console.error(" Regenerate failed:", err.message);
54
+ }
55
+ };
56
+ const onChange = (_event, filename) => {
57
+ if (filename && filename.startsWith("client")) return;
58
+ if (debounceTimer) clearTimeout(debounceTimer);
59
+ debounceTimer = setTimeout(() => {
60
+ console.log(` Change detected: ${filename ?? "unknown"}`);
61
+ regenerate();
62
+ }, 150);
63
+ };
64
+ try {
65
+ (0, node_fs.watch)(modelsDir, { recursive: true }, onChange);
66
+ } catch {}
67
+ try {
68
+ (0, node_fs.watch)(contentDir, { recursive: true }, onChange);
69
+ } catch {}
70
+ try {
71
+ (0, node_fs.watch)(configFile, onChange);
72
+ } catch {}
73
+ console.log("");
74
+ console.log("Watching for changes in .contentrain/ ...");
75
+ console.log("Press Ctrl+C to stop.");
76
+ }
77
+ main();
78
+ //#endregion
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };