@contentrain/query 4.0.0 → 5.0.1

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,260 +1,253 @@
1
- # @contentrain/query
2
-
3
- Core package of the Contentrain SDK. Originally designed for JSON-based content management, now extended with SQLite integration for enhanced performance and scalability.
4
-
5
- ## Features
6
-
7
- ### Core Features (JSON-based)
8
- - 📦 JSON file-based content management
9
- - 🌍 Multi-language support with JSON files
10
- - 💾 LRU caching with size-based eviction
11
- - 🔍 Type-safe query operations
12
- - 📝 Full TypeScript support
13
-
14
- ### SQLite Extension Features
15
- - 🚀 High-performance SQLite database integration
16
- - 🔄 Advanced relation management (One-to-One & One-to-Many)
17
- - 🗃️ Efficient data indexing and querying
18
- - 🔒 Thread-safe database operations
19
- - 📊 Advanced filtering and sorting
20
- - 🎯 Efficient pagination
21
- - 🌐 Enhanced translation support
22
-
23
- ## Installation
24
-
25
- ```bash
26
- # Using npm
27
- npm install @contentrain/query
28
-
29
- # Using yarn
30
- yarn add @contentrain/query
31
-
32
- # Using pnpm
33
- pnpm add @contentrain/query
34
- ```
35
-
36
- ## Usage
37
-
38
- ### JSON-based Usage (Original)
39
-
40
- ```typescript
41
- import { ContentrainSDK } from '@contentrain/query';
42
-
43
- // Initialize SDK with JSON files
44
- const sdk = new ContentrainSDK({
45
- contentDir: './content', // Directory containing JSON files
46
- defaultLocale: 'en'
47
- });
48
-
49
- // Query JSON content
50
- const posts = await sdk.query('posts')
51
- .where('status', 'eq', 'publish')
52
- .get();
53
-
54
- // Load with translations
55
- const trPosts = await sdk.query('posts')
56
- .locale('tr')
57
- .get();
58
- ```
59
-
60
- ### SQLite Usage (Extended Feature)
61
-
62
- ```typescript
63
- import { SQLiteQueryBuilder, BaseSQLiteLoader } from '@contentrain/query';
64
-
65
- // Initialize SQLite loader
66
- const loader = new BaseSQLiteLoader('path/to/database.db');
67
-
68
- // Create SQLite query builder
69
- const builder = new SQLiteQueryBuilder('posts', loader);
70
-
71
- // Execute SQLite query
72
- const result = await builder
73
- .where('status', 'eq', 'publish')
74
- .get();
75
- ```
76
-
77
- ## Data Management
78
-
79
- ### JSON-based Storage
80
- - Content stored in JSON files
81
- - Directory-based organization
82
- - File-based translations
83
- - Simple version control with Git
84
-
85
- ### SQLite Storage
86
- - Relational database storage
87
- - Optimized for querying and relations
88
- - Efficient data indexing
89
- - Better performance for large datasets
90
-
91
- ## Relations
92
-
93
- ### JSON Relations
94
- ```typescript
95
- // JSON-based relation loading
96
- const posts = await sdk.query('posts')
97
- .include('author')
98
- .get();
99
- ```
100
-
101
- ### SQLite Relations
102
- ```typescript
103
- interface Post {
104
- id: string;
105
- title: string;
106
- author_id: string;
107
- _relations?: {
108
- author: Author;
109
- categories: Category[];
110
- }
111
- }
112
-
113
- // One-to-One in SQLite
114
- const post = await builder
115
- .include('author')
116
- .where('id', 'eq', '123')
117
- .first();
118
-
119
- // One-to-Many in SQLite
120
- const postWithCategories = await builder
121
- .include('categories')
122
- .where('id', 'eq', '123')
123
- .first();
124
- ```
125
-
126
- ## Translations
127
-
128
- ### JSON Translations
129
- - Separate JSON files for each locale
130
- - File-based translation management
131
- - Git-friendly structure
132
-
133
- ### SQLite Translations
134
- ```typescript
135
- // Dedicated translation tables
136
- const trPost = await builder
137
- .locale('tr')
138
- .where('id', 'eq', '123')
139
- .first();
140
-
141
- // Fallback support
142
- const result = await builder
143
- .locale('tr')
144
- .include(['author', 'categories'])
145
- .get();
146
- ```
147
-
148
- ## API Reference
149
-
150
- ### ContentrainSDK (JSON-based)
151
- ```typescript
152
- class ContentrainSDK {
153
- constructor(options: ContentLoaderOptions)
154
- query<T extends QueryConfig>(model: string): ContentrainQueryBuilder<T>
155
- load<T>(model: string): Promise<LoaderResult<T>>
156
- }
157
- ```
158
-
159
- ### SQLiteQueryBuilder (SQLite Extension)
160
- ```typescript
161
- class SQLiteQueryBuilder<T extends DBRecord> {
162
- constructor(model: string, connection: BaseSQLiteLoader)
163
-
164
- // Query Methods
165
- where<K extends keyof T>(
166
- field: K,
167
- operator: Operator,
168
- value: T[K] | T[K][]
169
- ): this
170
-
171
- include(relations: string | string[]): this
172
- orderBy(field: keyof T, direction?: 'asc' | 'desc'): this
173
- limit(count: number): this
174
- offset(count: number): this
175
- locale(code: string): this
176
-
177
- // Execution Methods
178
- get(): Promise<QueryResult<T>>
179
- first(): Promise<T | null>
180
- count(): Promise<number>
181
- }
182
- ```
183
-
184
- ## Best Practices
185
-
186
- ### JSON vs SQLite Usage
187
-
188
- ```typescript
189
- // Use JSON when:
190
- // - Small to medium dataset
191
- // - Git-based version control is priority
192
- // - Simple content structure
193
- const posts = await sdk.query('posts').get();
194
-
195
- // ✅ Use SQLite when:
196
- // - Large dataset
197
- // - Complex relations
198
- // - Performance is critical
199
- // - Advanced querying needed
200
- const posts = await builder
201
- .where('status', 'eq', 'publish')
202
- .include(['author', 'categories'])
203
- .orderBy('created_at', 'desc')
204
- .limit(10)
205
- .get();
206
- ```
207
-
208
- ### Performance Considerations
209
-
210
- #### JSON Storage
211
- - Keep files organized in directories
212
- - Use appropriate file naming
213
- - Consider file size for large datasets
214
-
215
- #### SQLite Storage
216
- - Use appropriate indexes
217
- - Optimize relation queries
218
- - Implement pagination
219
- - Use eager loading for relations
220
-
221
- ## Error Handling
222
-
223
- ```typescript
224
- try {
225
- const result = await builder
226
- .where('status', 'eq', 'publish')
227
- .get();
228
- } catch (error) {
229
- if (error instanceof SQLiteError) {
230
- // Handle SQLite specific errors
231
- } else if (error instanceof ValidationError) {
232
- // Handle validation errors
233
- } else if (error instanceof RelationError) {
234
- // Handle relation errors
235
- } else if (error instanceof FileSystemError) {
236
- // Handle JSON file system errors
237
- }
238
- }
239
- ```
240
-
241
- ## Migration Guide
242
-
243
- ### From JSON to SQLite
244
- 1. Initialize SQLite database
245
- 2. Import JSON content
246
- 3. Set up relations
247
- 4. Update queries to use SQLiteQueryBuilder
248
- 5. Test and verify data integrity
249
-
250
- ## Contributing
251
-
252
- 1. Fork the repository
253
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
254
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
255
- 4. Push to the branch (`git push origin feature/amazing-feature`)
256
- 5. Open a Pull Request
257
-
258
- ## License
259
-
260
- MIT
1
+ # `@contentrain/query`
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40contentrain%2Fquery?label=%40contentrain%2Fquery)](https://www.npmjs.com/package/@contentrain/query)
4
+ [![GitHub source](https://img.shields.io/badge/source-Contentrain%2Fai-181717?logo=github)](https://github.com/Contentrain/ai/tree/main/packages/sdk/js)
5
+
6
+ **Optional** type-safe generated query SDK for Contentrain.
7
+
8
+ Contentrain stores content as plain JSON and Markdown in a git-backed `.contentrain/` directory. Any platform that reads JSON can consume this content directly. This package adds a TypeScript convenience layer that turns content models into a generated JS/TS client with:
9
+
10
+ - exact TypeScript types from your models
11
+ - zero-dependency query runtime
12
+ - Node `#contentrain` subpath imports
13
+ - ESM and CommonJS output
14
+ - framework-agnostic usage across app and server environments
15
+
16
+ ## 🚀 Install
17
+
18
+ ```bash
19
+ pnpm add @contentrain/query
20
+ ```
21
+
22
+ Requirements:
23
+
24
+ - Node.js `22+`
25
+ - a Contentrain project with `.contentrain/config.json`
26
+
27
+ ## What This Package Provides
28
+
29
+ `@contentrain/query` has two roles:
30
+
31
+ 1. **Generator**
32
+ - reads `.contentrain/config.json`, models, and content files
33
+ - writes `.contentrain/client/`
34
+ - injects `#contentrain` imports into your `package.json`
35
+
36
+ 2. **Base runtime**
37
+ - exports low-level runtime classes for framework SDK authors
38
+ - exports `createContentrainClient(projectRoot?)` for loading a generated client module
39
+
40
+ ## 🚀 Quick Start
41
+
42
+ Generate a client:
43
+
44
+ ```bash
45
+ npx contentrain-query generate
46
+ ```
47
+
48
+ This writes:
49
+
50
+ ```text
51
+ .contentrain/client/
52
+ index.mjs
53
+ index.cjs
54
+ index.d.ts
55
+ data/*
56
+ ```
57
+
58
+ It also updates your `package.json` with:
59
+
60
+ ```json
61
+ {
62
+ "imports": {
63
+ "#contentrain": {
64
+ "types": "./.contentrain/client/index.d.ts",
65
+ "import": "./.contentrain/client/index.mjs",
66
+ "require": "./.contentrain/client/index.cjs",
67
+ "default": "./.contentrain/client/index.mjs"
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ Then use the generated client in your app:
74
+
75
+ ```ts
76
+ import { query, singleton, dictionary, document } from '#contentrain'
77
+
78
+ const posts = query('blog-post')
79
+ .locale('en')
80
+ .where('status', 'published')
81
+ .sort('title')
82
+ .all()
83
+
84
+ const hero = singleton('hero').locale('en').get()
85
+ const messages = dictionary('error-messages').locale('en').get()
86
+ const article = document('blog-article').locale('en').bySlug('welcome-post')
87
+ ```
88
+
89
+ ## 📦 Generated Client API
90
+
91
+ The generated client exposes four entry points:
92
+
93
+ ### `query(model)`
94
+
95
+ For collection models.
96
+
97
+ Supported methods:
98
+
99
+ - `locale(lang)`
100
+ - `where(field, value)`
101
+ - `sort(field, order?)`
102
+ - `limit(n)`
103
+ - `offset(n)`
104
+ - `include(...fields)`
105
+ - `first()`
106
+ - `all()`
107
+
108
+ ### `singleton(model)`
109
+
110
+ For singleton models.
111
+
112
+ Supported methods:
113
+
114
+ - `locale(lang)`
115
+ - `include(...fields)`
116
+ - `get()`
117
+
118
+ ### `dictionary(model)`
119
+
120
+ For dictionary models.
121
+
122
+ Supported methods:
123
+
124
+ - `locale(lang)`
125
+ - `get()`
126
+ - `get(key)`
127
+
128
+ ### `document(model)`
129
+
130
+ For markdown/document models.
131
+
132
+ Supported methods:
133
+
134
+ - `locale(lang)`
135
+ - `where(field, value)`
136
+ - `include(...fields)`
137
+ - `bySlug(slug)`
138
+ - `first()`
139
+ - `all()`
140
+
141
+ ## 🔗 Relations
142
+
143
+ Generated clients support relation resolution via `include(...)`.
144
+
145
+ Examples:
146
+
147
+ ```ts
148
+ const posts = query('blog-post')
149
+ .locale('en')
150
+ .include('author', 'tags')
151
+ .all()
152
+
153
+ const settings = singleton('site-settings')
154
+ .locale('en')
155
+ .include('featured_post')
156
+ .get()
157
+ ```
158
+
159
+ ## 🧱 Framework SDK Authors
160
+
161
+ The package root exports runtime primitives and an async loader:
162
+
163
+ ```ts
164
+ import { createContentrainClient } from '@contentrain/query'
165
+
166
+ const client = await createContentrainClient(process.cwd())
167
+ const posts = client.query('blog-post').locale('en').all()
168
+ ```
169
+
170
+ Public root exports:
171
+
172
+ - `QueryBuilder`
173
+ - `SingletonAccessor`
174
+ - `DictionaryAccessor`
175
+ - `DocumentQuery`
176
+ - `createContentrainClient`
177
+
178
+ ## 🧩 CommonJS Usage
179
+
180
+ Generated clients support CommonJS through `init()`:
181
+
182
+ ```js
183
+ const clientModule = require('#contentrain')
184
+ const client = await clientModule.init()
185
+
186
+ const hero = client.singleton('hero').get()
187
+ ```
188
+
189
+ ## 🛠 CLI
190
+
191
+ Generate once:
192
+
193
+ ```bash
194
+ npx contentrain-query generate
195
+ ```
196
+
197
+ Generate in watch mode:
198
+
199
+ ```bash
200
+ npx contentrain-query generate --watch
201
+ ```
202
+
203
+ Use a different project root:
204
+
205
+ ```bash
206
+ npx contentrain-query generate --root /path/to/project
207
+ ```
208
+
209
+ ## 📤 Package Exports
210
+
211
+ Main package:
212
+
213
+ - `@contentrain/query`
214
+
215
+ Generator entry:
216
+
217
+ - `@contentrain/query/generate`
218
+
219
+ ## 🧠 Design Constraints
220
+
221
+ This package intentionally:
222
+
223
+ - generates into `.contentrain/client/` instead of `node_modules`
224
+ - uses `package.json#imports` instead of custom alias plugins
225
+ - ships zero-dependency runtime classes
226
+ - keeps the runtime framework-agnostic
227
+ - treats generated client output as the primary consumer surface
228
+
229
+ ## 🛠 Development
230
+
231
+ From the monorepo root:
232
+
233
+ ```bash
234
+ pnpm --filter @contentrain/query build
235
+ pnpm --filter @contentrain/query test
236
+ pnpm --filter @contentrain/query typecheck
237
+ pnpm exec oxlint packages/sdk/js/src packages/sdk/js/tests
238
+ ```
239
+
240
+ ## 🔗 Related Packages
241
+
242
+ - `contentrain` — CLI that runs project initialization, validation, serve, and generation flows
243
+ - `@contentrain/mcp` local-first MCP server and core content workflow engine
244
+ - `@contentrain/types` shared schema and model definitions
245
+ - `@contentrain/rules` agent rules and prompts
246
+
247
+ ## 📚 Documentation
248
+
249
+ Full documentation at **[ai.contentrain.io/packages/sdk](https://ai.contentrain.io/packages/sdk)**.
250
+
251
+ ## 📄 License
252
+
253
+ 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 { };