@contentrain/query 4.0.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,260 +1,246 @@
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
+ 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 { };