@contentrain/skills 0.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.
@@ -0,0 +1,296 @@
1
+ # Contentrain + Next.js 14+ (App Router)
2
+
3
+ > Framework guide for consuming Contentrain-managed content in Next.js applications using the App Router.
4
+
5
+ ---
6
+
7
+ ## 1. Setup
8
+
9
+ ### 1.1 SDK Installation
10
+
11
+ ```bash
12
+ pnpm add @contentrain/query # or npm/yarn
13
+ npx contentrain generate
14
+ ```
15
+
16
+ The generator reads `.contentrain/models/` and produces a typed client in `.contentrain/client/`. Your `package.json` must include the subpath import:
17
+
18
+ ```json
19
+ {
20
+ "imports": {
21
+ "#contentrain": {
22
+ "types": "./.contentrain/client/index.d.ts",
23
+ "import": "./.contentrain/client/index.mjs",
24
+ "require": "./.contentrain/client/index.cjs",
25
+ "default": "./.contentrain/client/index.mjs"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ The generator adds this automatically. Restart the Next.js dev server after initial generation.
32
+
33
+ ### 1.2 Watch Mode
34
+
35
+ Run the generator alongside Next.js dev:
36
+
37
+ ```bash
38
+ npx contentrain generate --watch &
39
+ npx next dev
40
+ ```
41
+
42
+ ---
43
+
44
+ ## 2. Imports
45
+
46
+ All SDK functions are imported from the `#contentrain` subpath:
47
+
48
+ ```ts
49
+ import { query, singleton, dictionary, document } from '#contentrain'
50
+ ```
51
+
52
+ This import works in React Server Components, server actions, route handlers, and `generateStaticParams`. The SDK reads JSON files from the file system — no runtime API calls.
53
+
54
+ ---
55
+
56
+ ## 3. Querying Content
57
+
58
+ ### 3.1 Collections
59
+
60
+ ```ts
61
+ const posts = query('blog-post').locale('en').all()
62
+ const featured = query('blog-post').locale('en').where('featured', true).all()
63
+ const sorted = query('blog-post').locale('en').sort('publishedAt', 'desc').all()
64
+ const post = query('blog-post').locale('en').where('id', 'abc-123').first()
65
+ ```
66
+
67
+ ### 3.2 Singletons
68
+
69
+ ```ts
70
+ const hero = singleton('hero').locale('en').get()
71
+ ```
72
+
73
+ ### 3.3 Dictionaries
74
+
75
+ ```ts
76
+ const label = dictionary('ui-labels').locale('en').get('submit_button')
77
+ const allLabels = dictionary('ui-labels').locale('en').get()
78
+ ```
79
+
80
+ ### 3.4 Documents
81
+
82
+ ```ts
83
+ const article = document('blog-article').locale('en').bySlug('getting-started')
84
+ ```
85
+
86
+ ### 3.5 Relations
87
+
88
+ ```ts
89
+ const posts = query('blog-post').locale('en').include('author', 'tags').all()
90
+ // post.author → full author object
91
+ // post.tags → array of full tag objects
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 4. App Router Integration
97
+
98
+ ### 4.1 React Server Components
99
+
100
+ SDK calls work directly in Server Components — no `use` wrapper, no `useEffect`, no client-side fetching:
101
+
102
+ ```tsx
103
+ // app/blog/page.tsx
104
+ import { query } from '#contentrain'
105
+
106
+ export default async function BlogPage() {
107
+ const posts = query('blog-post').locale('en').sort('publishedAt', 'desc').all()
108
+
109
+ return (
110
+ <ul>
111
+ {posts.map(post => (
112
+ <li key={post.id}>{post.title}</li>
113
+ ))}
114
+ </ul>
115
+ )
116
+ }
117
+ ```
118
+
119
+ ### 4.2 Dynamic Routes with Locale
120
+
121
+ For `app/[locale]/blog/[slug]/page.tsx`:
122
+
123
+ ```tsx
124
+ import { document } from '#contentrain'
125
+
126
+ interface Props {
127
+ params: Promise<{ locale: string; slug: string }>
128
+ }
129
+
130
+ export default async function PostPage({ params }: Props) {
131
+ const { locale, slug } = await params
132
+ const post = document('blog-article').locale(locale).bySlug(slug)
133
+
134
+ return (
135
+ <article>
136
+ <h1>{post.title}</h1>
137
+ <div>{post.body}</div>
138
+ </article>
139
+ )
140
+ }
141
+ ```
142
+
143
+ ### 4.3 Static Params Generation
144
+
145
+ Pre-generate all content pages at build time:
146
+
147
+ ```tsx
148
+ import { query } from '#contentrain'
149
+
150
+ export async function generateStaticParams() {
151
+ const posts = query('blog-post').locale('en').all()
152
+ return posts.map(post => ({ slug: post.slug }))
153
+ }
154
+ ```
155
+
156
+ For multi-locale static generation:
157
+
158
+ ```tsx
159
+ import { query } from '#contentrain'
160
+
161
+ const locales = ['en', 'tr', 'de']
162
+
163
+ export async function generateStaticParams() {
164
+ const params = []
165
+ for (const locale of locales) {
166
+ const posts = query('blog-post').locale(locale).all()
167
+ for (const post of posts) {
168
+ params.push({ locale, slug: post.slug })
169
+ }
170
+ }
171
+ return params
172
+ }
173
+ ```
174
+
175
+ ### 4.4 Route Handlers
176
+
177
+ SDK works in route handlers for API-style access:
178
+
179
+ ```ts
180
+ // app/api/posts/route.ts
181
+ import { query } from '#contentrain'
182
+ import { NextResponse } from 'next/server'
183
+
184
+ export function GET() {
185
+ const posts = query('blog-post').locale('en').all()
186
+ return NextResponse.json(posts)
187
+ }
188
+ ```
189
+
190
+ ### 4.5 Client Components
191
+
192
+ Client Components cannot import `#contentrain` directly (it reads from the file system). Fetch data in a Server Component and pass it as props:
193
+
194
+ ```tsx
195
+ // Server Component
196
+ import { singleton } from '#contentrain'
197
+ import HeroClient from './HeroClient'
198
+
199
+ export default async function HeroSection() {
200
+ const hero = singleton('hero').locale('en').get()
201
+ return <HeroClient data={hero} />
202
+ }
203
+ ```
204
+
205
+ ---
206
+
207
+ ## 5. i18n Integration
208
+
209
+ ### 5.1 With next-intl
210
+
211
+ ```tsx
212
+ import { query } from '#contentrain'
213
+ import { getLocale } from 'next-intl/server'
214
+
215
+ export default async function BlogPage() {
216
+ const locale = await getLocale()
217
+ const posts = query('blog-post').locale(locale).all()
218
+ // ...
219
+ }
220
+ ```
221
+
222
+ ### 5.2 With Built-in i18n Routing
223
+
224
+ Use the `[locale]` route segment to determine the active locale, then pass it to SDK calls. Contentrain dictionaries can supplement or replace `next-intl` message files for content-driven strings.
225
+
226
+ ---
227
+
228
+ ## 6. Markdown and MDX
229
+
230
+ ### 6.1 Document Body Rendering
231
+
232
+ For document-kind entries with markdown bodies, use `next-mdx-remote` or a markdown renderer:
233
+
234
+ ```tsx
235
+ import { document } from '#contentrain'
236
+ import { MDXRemote } from 'next-mdx-remote/rsc'
237
+
238
+ export default async function DocPage({ params }: Props) {
239
+ const { slug } = await params
240
+ const doc = document('doc-page').locale('en').bySlug(slug)
241
+
242
+ return (
243
+ <article>
244
+ <h1>{doc.title}</h1>
245
+ <MDXRemote source={doc.body} />
246
+ </article>
247
+ )
248
+ }
249
+ ```
250
+
251
+ ### 6.2 Custom Components in MDX
252
+
253
+ Pass custom component mappings to the MDX renderer to control how markdown elements render in your design system.
254
+
255
+ ---
256
+
257
+ ## 7. Build and Deployment
258
+
259
+ ### 7.1 Static Export
260
+
261
+ Contentrain content is fully static. Use `output: 'export'` in `next.config.js` for full static generation:
262
+
263
+ ```js
264
+ // next.config.js
265
+ module.exports = {
266
+ output: 'export'
267
+ }
268
+ ```
269
+
270
+ ### 7.2 ISR and Server Mode
271
+
272
+ In server mode, SDK calls execute at request time (or at build time for statically generated pages). Content is still file-based — no external API dependency. Redeploy to pick up content changes.
273
+
274
+ ### 7.3 Deployment Flow
275
+
276
+ 1. Content changes are committed to Git via MCP tools.
277
+ 2. Push triggers platform rebuild (Vercel, Netlify).
278
+ 3. `next build` runs, SDK reads `.contentrain/` content.
279
+ 4. Static pages are generated with embedded content.
280
+
281
+ ---
282
+
283
+ ## 8. Type Safety
284
+
285
+ The generated client provides full TypeScript types:
286
+
287
+ ```tsx
288
+ import { query } from '#contentrain'
289
+
290
+ // Fully typed — autocomplete for all fields
291
+ const posts = query('blog-post').locale('en').all()
292
+ posts[0].title // string
293
+ posts[0].author // Author type when using .include('author')
294
+ ```
295
+
296
+ Keep the generator running in watch mode during development to stay in sync with model changes.
@@ -0,0 +1,63 @@
1
+ # Contentrain + Node.js
2
+
3
+ > Framework guide for direct server-side use of the generated Contentrain client.
4
+
5
+ ---
6
+
7
+ ## 1. ESM Usage
8
+
9
+ For ESM apps, import from `#contentrain` directly:
10
+
11
+ ```ts
12
+ import { query, singleton, dictionary, document } from '#contentrain'
13
+
14
+ const hero = singleton('hero').locale('en').get()
15
+ const posts = query('blog-post').locale('en').all()
16
+ const labels = dictionary('ui-labels').locale('en').get()
17
+ const article = document('blog-article').locale('en').bySlug('getting-started')
18
+ ```
19
+
20
+ This is the correct path for:
21
+
22
+ - modern Node ESM apps
23
+ - server scripts
24
+ - Express/Fastify codebases running as ESM
25
+
26
+ ## 2. CommonJS Usage
27
+
28
+ For CommonJS consumers, initialize first:
29
+
30
+ ```js
31
+ async function run() {
32
+ const client = await require('#contentrain').init()
33
+ const hero = client.singleton('hero').locale('en').get()
34
+ const posts = client.query('blog-post').locale('en').all()
35
+ return { hero, posts }
36
+ }
37
+ ```
38
+
39
+ Use this pattern for:
40
+
41
+ - NestJS
42
+ - legacy CommonJS servers
43
+ - mixed CJS toolchains
44
+
45
+ ## 3. Express Example
46
+
47
+ ```ts
48
+ import express from 'express'
49
+ import { query } from '#contentrain'
50
+
51
+ const app = express()
52
+
53
+ app.get('/api/posts', (_req, res) => {
54
+ res.json(query('blog-post').locale('en').all())
55
+ })
56
+ ```
57
+
58
+ ## 4. Rules
59
+
60
+ - ESM: direct `import` from `#contentrain`
61
+ - CJS: `await require('#contentrain').init()`
62
+ - do not expect runtime writes; this is generated static content access
63
+
@@ -0,0 +1,274 @@
1
+ # Contentrain + Nuxt 3
2
+
3
+ > Framework guide for consuming Contentrain-managed content in Nuxt 3 applications.
4
+
5
+ ---
6
+
7
+ ## 1. Setup
8
+
9
+ ### 1.1 SDK Installation
10
+
11
+ ```bash
12
+ pnpm add @contentrain/query # or npm/yarn
13
+ npx contentrain generate
14
+ ```
15
+
16
+ The generator reads `.contentrain/models/` and produces a typed client in `.contentrain/client/`. Your `package.json` must include the subpath import:
17
+
18
+ ```json
19
+ {
20
+ "imports": {
21
+ "#contentrain": {
22
+ "types": "./.contentrain/client/index.d.ts",
23
+ "import": "./.contentrain/client/index.mjs",
24
+ "require": "./.contentrain/client/index.cjs",
25
+ "default": "./.contentrain/client/index.mjs"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ The generator adds this automatically. After generation, restart the Nuxt dev server to pick up the new import map.
32
+
33
+ ### 1.2 Watch Mode
34
+
35
+ Run the generator in watch mode alongside Nuxt dev:
36
+
37
+ ```bash
38
+ npx contentrain generate --watch &
39
+ npx nuxt dev
40
+ ```
41
+
42
+ Any model or content change under `.contentrain/` triggers client regeneration automatically.
43
+
44
+ ---
45
+
46
+ ## 2. Imports
47
+
48
+ All SDK functions are imported from the `#contentrain` subpath:
49
+
50
+ ```ts
51
+ import { query, singleton, dictionary, document } from '#contentrain'
52
+ ```
53
+
54
+ Treat this import as **server-only** in Nuxt. Use it in Nitro server routes, server utilities, route middleware, and other server execution contexts. Do **not** import `#contentrain` directly in client-rendered Vue components or generic `<script setup>` blocks.
55
+
56
+ ---
57
+
58
+ ## 3. Querying Content
59
+
60
+ ### 3.1 Collections
61
+
62
+ Collections return arrays. The SDK converts the internal object-map storage to arrays automatically.
63
+
64
+ ```ts
65
+ const posts = query('blog-post').locale('en').all()
66
+ const featured = query('blog-post').locale('en').where('featured', true).all()
67
+ const sorted = query('blog-post').locale('en').sort('publishedAt', 'desc').all()
68
+ ```
69
+
70
+ Single entry by ID:
71
+
72
+ ```ts
73
+ const post = query('blog-post').locale('en').where('id', 'abc-123').first()
74
+ ```
75
+
76
+ ### 3.2 Singletons
77
+
78
+ Singletons return a single object. Use `.get()` instead of `.all()`.
79
+
80
+ ```ts
81
+ const hero = singleton('hero').locale('en').get()
82
+ ```
83
+
84
+ ### 3.3 Dictionaries
85
+
86
+ Dictionaries provide key-value access for UI strings.
87
+
88
+ ```ts
89
+ const labels = dictionary('ui-labels').locale('en').get('submit_button')
90
+ const allLabels = dictionary('ui-labels').locale('en').get()
91
+ ```
92
+
93
+ ### 3.4 Documents
94
+
95
+ Documents are long-form content with metadata and a body field (markdown or MDX).
96
+
97
+ ```ts
98
+ const article = document('blog-article').locale('en').bySlug('getting-started')
99
+ const docs = document('doc-page').locale('en').all()
100
+ ```
101
+
102
+ ### 3.5 Relations
103
+
104
+ Resolve relation fields to full objects with `.include()`:
105
+
106
+ ```ts
107
+ const posts = query('blog-post').locale('en').include('author', 'tags').all()
108
+ // post.author → full author object instead of just the ID
109
+ // post.tags → array of full tag objects
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 4. Nuxt Integration Patterns
115
+
116
+ ### 4.1 Page Data Loading
117
+
118
+ Load content through a server route, then consume it with `useAsyncData` in the page:
119
+
120
+ ```ts
121
+ // server/api/blog-posts.get.ts
122
+ import { query } from '#contentrain'
123
+ import { getQuery } from 'h3'
124
+
125
+ export default defineEventHandler((event) => {
126
+ const locale = getQuery(event).locale?.toString() ?? 'en'
127
+ return query('blog-post').locale(locale).sort('publishedAt', 'desc').all()
128
+ })
129
+ ```
130
+
131
+ ```vue
132
+ <script setup lang="ts">
133
+ const { data: posts } = await useAsyncData('blog-posts', () => $fetch('/api/blog-posts'))
134
+ </script>
135
+ ```
136
+
137
+ ### 4.2 Dynamic Routes
138
+
139
+ For `pages/blog/[slug].vue`:
140
+
141
+ ```vue
142
+ <script setup lang="ts">
143
+ const route = useRoute()
144
+ const { data: post } = await useAsyncData(
145
+ `post-${route.params.slug}`,
146
+ () => $fetch(`/api/blog-post/${route.params.slug}`),
147
+ )
148
+ </script>
149
+ ```
150
+
151
+ With the matching server route:
152
+
153
+ ```ts
154
+ // server/api/blog-post/[slug].get.ts
155
+ import { document } from '#contentrain'
156
+ import { getRouterParam } from 'h3'
157
+
158
+ export default defineEventHandler((event) => {
159
+ const slug = getRouterParam(event, 'slug')
160
+ return document('blog-article').locale('en').bySlug(slug ?? '')
161
+ })
162
+ ```
163
+
164
+ ### 4.3 Server Routes
165
+
166
+ Content is also accessible in Nuxt server routes (`server/api/`):
167
+
168
+ ```ts
169
+ // server/api/posts.get.ts
170
+ import { query } from '#contentrain'
171
+
172
+ export default defineEventHandler(() => {
173
+ return query('blog-post').locale('en').all()
174
+ })
175
+ ```
176
+
177
+ ---
178
+
179
+ ## 5. i18n Integration
180
+
181
+ ### 5.1 With @nuxtjs/i18n
182
+
183
+ Use the `useI18n()` composable to get the current locale, then pass it to SDK calls:
184
+
185
+ ```vue
186
+ <script setup lang="ts">
187
+ const { locale } = useI18n()
188
+ const { data: posts } = await useAsyncData(
189
+ `posts-${locale.value}`,
190
+ () => $fetch('/api/blog-posts', { query: { locale: locale.value } }),
191
+ { watch: [locale] }
192
+ )
193
+ </script>
194
+ ```
195
+
196
+ ### 5.2 Dictionary Strings as i18n Source
197
+
198
+ Contentrain dictionaries can serve as the i18n message source. Export dictionary content and feed it to `@nuxtjs/i18n` via a custom loader, or use SDK calls directly in components for content-driven strings.
199
+
200
+ ---
201
+
202
+ ## 6. Markdown and Document Content
203
+
204
+ ### 6.1 Nuxt Content Module
205
+
206
+ If using `@nuxt/content`, point it to `.contentrain/content/` for markdown files:
207
+
208
+ ```ts
209
+ // nuxt.config.ts
210
+ export default defineNuxtConfig({
211
+ content: {
212
+ sources: {
213
+ contentrain: {
214
+ driver: 'fs',
215
+ base: '.contentrain/content'
216
+ }
217
+ }
218
+ }
219
+ })
220
+ ```
221
+
222
+ ### 6.2 Rendering Document Bodies
223
+
224
+ For document-kind entries, the `body` field contains markdown. Render it with `@nuxt/content` or any markdown renderer:
225
+
226
+ ```vue
227
+ <template>
228
+ <ContentRenderer :value="parsedBody" />
229
+ </template>
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 7. Static Generation and Deployment
235
+
236
+ ### 7.1 Static Generation
237
+
238
+ Contentrain content is file-based. `nuxt generate` reads all content at build time:
239
+
240
+ ```bash
241
+ npx nuxt generate
242
+ ```
243
+
244
+ All SDK calls resolve to static JSON reads — no runtime API calls during generation.
245
+
246
+ ### 7.2 Deployment
247
+
248
+ Content lives in Git. The deployment flow:
249
+
250
+ 1. Author or agent creates/updates content via MCP tools.
251
+ 2. Changes are committed to a branch and reviewed.
252
+ 3. After merge, push triggers platform rebuild (Vercel, Netlify, Cloudflare Pages).
253
+ 4. `nuxt generate` runs, SDK reads `.contentrain/` content, static site is produced.
254
+
255
+ ### 7.3 Hybrid Rendering
256
+
257
+ For SSR pages, SDK calls execute on the server at request time. Content is still read from the file system — no external API dependency.
258
+
259
+ ---
260
+
261
+ ## 8. Type Safety
262
+
263
+ The generated client includes full TypeScript types for every model:
264
+
265
+ ```ts
266
+ import { query } from '#contentrain'
267
+
268
+ // Fully typed — IDE autocomplete for field names, return types
269
+ const posts = query('blog-post').locale('en').all()
270
+ // posts[0].title — string
271
+ // posts[0].author — Author (when using .include('author'))
272
+ ```
273
+
274
+ Type definitions are regenerated whenever models change. Keep the generator running in watch mode during development.
@@ -0,0 +1,80 @@
1
+ # Contentrain + React Native
2
+
3
+ > Framework guide for bare React Native / Metro consumers of the generated Contentrain client.
4
+
5
+ ---
6
+
7
+ ## 1. Setup
8
+
9
+ ```bash
10
+ pnpm add @contentrain/query
11
+ npx contentrain generate
12
+ ```
13
+
14
+ React Native uses the generated CommonJS bootstrap path. Do not expect direct ESM `#contentrain` imports inside Metro components.
15
+
16
+ ---
17
+
18
+ ## 2. Bootstrap the Client Once
19
+
20
+ Create a small loader module:
21
+
22
+ ```ts
23
+ // src/contentrain.ts
24
+ let clientPromise: Promise<any> | null = null
25
+
26
+ export function getContentrainClient() {
27
+ if (!clientPromise) {
28
+ clientPromise = require('#contentrain').init()
29
+ }
30
+ return clientPromise
31
+ }
32
+ ```
33
+
34
+ This ensures the generated client is initialized once and reused across screens.
35
+
36
+ ---
37
+
38
+ ## 3. Use It in Screens
39
+
40
+ ```tsx
41
+ import { useEffect, useState } from 'react'
42
+ import { Text, View } from 'react-native'
43
+ import { getContentrainClient } from './contentrain'
44
+
45
+ export function HomeScreen() {
46
+ const [heroTitle, setHeroTitle] = useState('')
47
+
48
+ useEffect(() => {
49
+ getContentrainClient().then((client) => {
50
+ setHeroTitle(client.singleton('hero').locale('en').get().title)
51
+ })
52
+ }, [])
53
+
54
+ return (
55
+ <View>
56
+ <Text>{heroTitle}</Text>
57
+ </View>
58
+ )
59
+ }
60
+ ```
61
+
62
+ Collections and dictionaries work the same way:
63
+
64
+ ```ts
65
+ getContentrainClient().then((client) => {
66
+ const posts = client.query('blog-post').locale('en').all()
67
+ const labels = client.dictionary('ui-labels').locale('en').get()
68
+ })
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 4. Rules
74
+
75
+ - initialize with `require('#contentrain').init()`
76
+ - cache the promise; do not initialize in every component render
77
+ - treat the generated client as static read-only data
78
+ - re-run `contentrain generate` after model or content changes
79
+
80
+ If the project is Expo-based, use the dedicated Expo guide. The runtime pattern is similar, but Expo-specific tooling and structure differ.