kaddidlehopper 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.
Files changed (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,505 @@
1
+ ---
2
+ name: content-collections
3
+ description: Use content-collections for type-safe content management with markdown files. Use when building blogs, documentation sites, or any content-driven pages with frontmatter and markdown.
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: sdorra
7
+ version: "1.0"
8
+ ---
9
+
10
+ # Content Collections
11
+
12
+ Content Collections transforms markdown and other content files into type-safe data collections with full TypeScript support.
13
+
14
+ ## When to Use
15
+
16
+ - Blog posts with frontmatter
17
+ - Documentation pages
18
+ - Content-driven sites
19
+ - Any structured content in markdown/JSON/YAML
20
+ - When you need type-safe content access
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @content-collections/core
26
+ npm install -D @content-collections/vite # For Vite/TanStack Start
27
+ ```
28
+
29
+ ## Basic Setup
30
+
31
+ ### Configuration File
32
+
33
+ ```typescript
34
+ // content-collections.ts
35
+ import { defineCollection, defineConfig } from '@content-collections/core';
36
+ import { z } from 'zod';
37
+
38
+ const posts = defineCollection({
39
+ name: 'posts',
40
+ directory: 'content/posts',
41
+ include: '**/*.md',
42
+ schema: (z) => ({
43
+ title: z.string(),
44
+ description: z.string().optional(),
45
+ published: z.string().date(),
46
+ author: z.string(),
47
+ tags: z.array(z.string()).optional(),
48
+ }),
49
+ });
50
+
51
+ export default defineConfig({
52
+ collections: [posts],
53
+ });
54
+ ```
55
+
56
+ ### Vite/TanStack Start Integration
57
+
58
+ ```typescript
59
+ // app.config.ts
60
+ import { defineConfig } from '@tanstack/react-start/config';
61
+ import contentCollections from '@content-collections/vite';
62
+
63
+ export default defineConfig({
64
+ vite: {
65
+ plugins: [contentCollections()],
66
+ },
67
+ });
68
+ ```
69
+
70
+ ## Content File Structure
71
+
72
+ ```
73
+ content/
74
+ ├── posts/
75
+ │ ├── hello-world.md
76
+ │ ├── getting-started.md
77
+ │ └── advanced-topics.md
78
+ └── docs/
79
+ ├── introduction.md
80
+ └── api-reference.md
81
+ ```
82
+
83
+ ### Markdown File Format
84
+
85
+ ```markdown
86
+ ---
87
+ title: Hello World
88
+ description: My first blog post
89
+ published: 2024-01-15
90
+ author: Alice
91
+ tags:
92
+ - introduction
93
+ - tutorial
94
+ ---
95
+
96
+ # Hello World
97
+
98
+ This is the content of my blog post.
99
+
100
+ ## Getting Started
101
+
102
+ Here's how to get started...
103
+ ```
104
+
105
+ ## Using Collections
106
+
107
+ ### Import Generated Data
108
+
109
+ ```typescript
110
+ // Collections are auto-generated
111
+ import { allPosts } from 'content-collections';
112
+
113
+ // allPosts is an array of typed post objects
114
+ allPosts.forEach((post) => {
115
+ console.log(post.title); // string
116
+ console.log(post.published); // string (date)
117
+ console.log(post.tags); // string[] | undefined
118
+ console.log(post._meta.path); // file path without extension
119
+ console.log(post.content); // raw markdown content
120
+ });
121
+ ```
122
+
123
+ ### In TanStack Start Routes
124
+
125
+ ```tsx
126
+ // src/routes/blog.tsx
127
+ import { createFileRoute } from '@tanstack/react-router';
128
+ import { allPosts } from 'content-collections';
129
+
130
+ export const Route = createFileRoute('/blog')({
131
+ loader: () => {
132
+ // Sort by date, newest first
133
+ const posts = allPosts
134
+ .sort((a, b) =>
135
+ new Date(b.published).getTime() - new Date(a.published).getTime()
136
+ );
137
+
138
+ return { posts };
139
+ },
140
+ component: BlogComponent,
141
+ });
142
+
143
+ function BlogComponent() {
144
+ const { posts } = Route.useLoaderData();
145
+
146
+ return (
147
+ <div>
148
+ <h1>Blog</h1>
149
+ <ul>
150
+ {posts.map((post) => (
151
+ <li key={post._meta.path}>
152
+ <Link to="/blog/$slug" params={{ slug: post._meta.path }}>
153
+ <h2>{post.title}</h2>
154
+ <p>{post.description}</p>
155
+ <time>{post.published}</time>
156
+ </Link>
157
+ </li>
158
+ ))}
159
+ </ul>
160
+ </div>
161
+ );
162
+ }
163
+ ```
164
+
165
+ ### Individual Post Page
166
+
167
+ ```tsx
168
+ // src/routes/blog.$slug.tsx
169
+ import { createFileRoute } from '@tanstack/react-router';
170
+ import { allPosts } from 'content-collections';
171
+
172
+ export const Route = createFileRoute('/blog/$slug')({
173
+ loader: ({ params }) => {
174
+ const post = allPosts.find((p) => p._meta.path === params.slug);
175
+
176
+ if (!post) {
177
+ throw new Error('Post not found');
178
+ }
179
+
180
+ return { post };
181
+ },
182
+ component: PostComponent,
183
+ });
184
+
185
+ function PostComponent() {
186
+ const { post } = Route.useLoaderData();
187
+
188
+ return (
189
+ <article>
190
+ <h1>{post.title}</h1>
191
+ <p>By {post.author} on {post.published}</p>
192
+
193
+ {/* Render markdown content */}
194
+ <div dangerouslySetInnerHTML={{ __html: post.html }} />
195
+ </article>
196
+ );
197
+ }
198
+ ```
199
+
200
+ ## Transforming Content
201
+
202
+ ### Compile Markdown to HTML
203
+
204
+ ```typescript
205
+ // content-collections.ts
206
+ import { defineCollection, defineConfig } from '@content-collections/core';
207
+ import { compileMarkdown } from '@content-collections/markdown';
208
+
209
+ const posts = defineCollection({
210
+ name: 'posts',
211
+ directory: 'content/posts',
212
+ include: '**/*.md',
213
+ schema: (z) => ({
214
+ title: z.string(),
215
+ published: z.string().date(),
216
+ }),
217
+ transform: async (document, context) => {
218
+ // Compile markdown to HTML
219
+ const html = await compileMarkdown(context, document);
220
+
221
+ return {
222
+ ...document,
223
+ html,
224
+ // Add computed fields
225
+ slug: document._meta.path,
226
+ readingTime: calculateReadingTime(document.content),
227
+ };
228
+ },
229
+ });
230
+
231
+ function calculateReadingTime(content: string): number {
232
+ const wordsPerMinute = 200;
233
+ const words = content.split(/\s+/).length;
234
+ return Math.ceil(words / wordsPerMinute);
235
+ }
236
+ ```
237
+
238
+ ### Install Markdown Package
239
+
240
+ ```bash
241
+ npm install @content-collections/markdown
242
+ ```
243
+
244
+ ### Advanced Markdown with Plugins
245
+
246
+ ```typescript
247
+ import { compileMarkdown } from '@content-collections/markdown';
248
+ import remarkGfm from 'remark-gfm';
249
+ import rehypeHighlight from 'rehype-highlight';
250
+
251
+ const posts = defineCollection({
252
+ name: 'posts',
253
+ directory: 'content/posts',
254
+ include: '**/*.md',
255
+ schema: (z) => ({
256
+ title: z.string(),
257
+ }),
258
+ transform: async (document, context) => {
259
+ const html = await compileMarkdown(context, document, {
260
+ remarkPlugins: [remarkGfm],
261
+ rehypePlugins: [rehypeHighlight],
262
+ allowDangerousHtml: true,
263
+ });
264
+
265
+ return { ...document, html };
266
+ },
267
+ });
268
+ ```
269
+
270
+ ## Multiple Collections
271
+
272
+ ```typescript
273
+ // content-collections.ts
274
+ import { defineCollection, defineConfig } from '@content-collections/core';
275
+
276
+ const posts = defineCollection({
277
+ name: 'posts',
278
+ directory: 'content/posts',
279
+ include: '**/*.md',
280
+ schema: (z) => ({
281
+ title: z.string(),
282
+ published: z.string().date(),
283
+ author: z.string(),
284
+ }),
285
+ });
286
+
287
+ const docs = defineCollection({
288
+ name: 'docs',
289
+ directory: 'content/docs',
290
+ include: '**/*.md',
291
+ schema: (z) => ({
292
+ title: z.string(),
293
+ order: z.number().optional(),
294
+ category: z.string().optional(),
295
+ }),
296
+ });
297
+
298
+ const authors = defineCollection({
299
+ name: 'authors',
300
+ directory: 'content/authors',
301
+ include: '**/*.json',
302
+ schema: (z) => ({
303
+ name: z.string(),
304
+ email: z.string().email(),
305
+ bio: z.string().optional(),
306
+ avatar: z.string().optional(),
307
+ }),
308
+ });
309
+
310
+ export default defineConfig({
311
+ collections: [posts, docs, authors],
312
+ });
313
+ ```
314
+
315
+ ### Usage
316
+
317
+ ```typescript
318
+ import { allPosts, allDocs, allAuthors } from 'content-collections';
319
+
320
+ // Each collection is independently typed
321
+ const post = allPosts[0];
322
+ const doc = allDocs[0];
323
+ const author = allAuthors[0];
324
+ ```
325
+
326
+ ## Joining Collections
327
+
328
+ ```typescript
329
+ // content-collections.ts
330
+ const posts = defineCollection({
331
+ name: 'posts',
332
+ directory: 'content/posts',
333
+ include: '**/*.md',
334
+ schema: (z) => ({
335
+ title: z.string(),
336
+ authorId: z.string(), // Reference to author
337
+ }),
338
+ transform: async (document, context) => {
339
+ // Find the author from the authors collection
340
+ const author = context
341
+ .documents(authors)
342
+ .find((a) => a._meta.path === document.authorId);
343
+
344
+ return {
345
+ ...document,
346
+ author: author ? {
347
+ name: author.name,
348
+ avatar: author.avatar,
349
+ } : null,
350
+ };
351
+ },
352
+ });
353
+ ```
354
+
355
+ ## _meta Object
356
+
357
+ Every document includes a `_meta` object:
358
+
359
+ ```typescript
360
+ {
361
+ _meta: {
362
+ path: "hello-world", // File path without extension
363
+ fileName: "hello-world.md",
364
+ directory: "content/posts",
365
+ extension: "md",
366
+ filePath: "content/posts/hello-world.md",
367
+ }
368
+ }
369
+ ```
370
+
371
+ ## Schema Validation
372
+
373
+ Content Collections uses Zod for schema validation:
374
+
375
+ ```typescript
376
+ const posts = defineCollection({
377
+ name: 'posts',
378
+ directory: 'content/posts',
379
+ include: '**/*.md',
380
+ schema: (z) => ({
381
+ // Required fields
382
+ title: z.string().min(1).max(100),
383
+ published: z.string().date(),
384
+
385
+ // Optional fields
386
+ description: z.string().optional(),
387
+ draft: z.boolean().default(false),
388
+
389
+ // Arrays
390
+ tags: z.array(z.string()).default([]),
391
+
392
+ // Enums
393
+ category: z.enum(['tech', 'life', 'tutorial']),
394
+
395
+ // Complex types
396
+ author: z.object({
397
+ name: z.string(),
398
+ email: z.string().email(),
399
+ }),
400
+
401
+ // Coercion
402
+ views: z.coerce.number().default(0),
403
+ }),
404
+ });
405
+ ```
406
+
407
+ ## Development Workflow
408
+
409
+ ### Hot Module Replacement
410
+
411
+ Content Collections supports HMR - changes to content files automatically update:
412
+
413
+ ```bash
414
+ npm run dev
415
+ # Edit content/posts/hello-world.md
416
+ # Changes appear instantly in browser
417
+ ```
418
+
419
+ ### Build Validation
420
+
421
+ Invalid content fails the build:
422
+
423
+ ```bash
424
+ npm run build
425
+ # Error: posts/bad-post.md - "published" is required
426
+ ```
427
+
428
+ ## Directory Structure Best Practice
429
+
430
+ ```
431
+ project/
432
+ ├── content/
433
+ │ ├── posts/
434
+ │ │ ├── 2024/
435
+ │ │ │ ├── hello-world.md
436
+ │ │ │ └── another-post.md
437
+ │ │ └── 2023/
438
+ │ │ └── old-post.md
439
+ │ ├── docs/
440
+ │ │ ├── getting-started.md
441
+ │ │ └── api/
442
+ │ │ └── reference.md
443
+ │ └── authors/
444
+ │ ├── alice.json
445
+ │ └── bob.json
446
+ ├── content-collections.ts
447
+ ├── app.config.ts
448
+ └── src/
449
+ └── routes/
450
+ ```
451
+
452
+ ## TypeScript Support
453
+
454
+ Full type inference for all collections:
455
+
456
+ ```typescript
457
+ import { allPosts } from 'content-collections';
458
+ import type { Post } from 'content-collections';
459
+
460
+ // Type is inferred
461
+ const post = allPosts[0];
462
+ post.title; // string
463
+ post.published; // string
464
+ post.tags; // string[] | undefined
465
+
466
+ // Or use the generated type
467
+ function renderPost(post: Post) {
468
+ return <h1>{post.title}</h1>;
469
+ }
470
+ ```
471
+
472
+ ## Common Patterns
473
+
474
+ ### Filter Published Posts
475
+
476
+ ```typescript
477
+ const publishedPosts = allPosts.filter((post) => !post.draft);
478
+ ```
479
+
480
+ ### Sort by Date
481
+
482
+ ```typescript
483
+ const sortedPosts = allPosts.sort(
484
+ (a, b) => new Date(b.published).getTime() - new Date(a.published).getTime()
485
+ );
486
+ ```
487
+
488
+ ### Group by Category
489
+
490
+ ```typescript
491
+ const postsByCategory = allPosts.reduce((acc, post) => {
492
+ const category = post.category || 'uncategorized';
493
+ acc[category] = acc[category] || [];
494
+ acc[category].push(post);
495
+ return acc;
496
+ }, {} as Record<string, typeof allPosts>);
497
+ ```
498
+
499
+ ### Get Post by Slug
500
+
501
+ ```typescript
502
+ function getPostBySlug(slug: string) {
503
+ return allPosts.find((post) => post._meta.path === slug);
504
+ }
505
+ ```