@nuasite/cms 0.26.0 → 0.28.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/README.md CHANGED
@@ -213,6 +213,96 @@ The integration auto-detects Astro content collections in `src/content/`. For ea
213
213
  - Provides markdown CRUD endpoints for creating/updating entries
214
214
  - Parses frontmatter with `yaml` (no `gray-matter` dependency needed)
215
215
 
216
+ ### Schema Helpers (`n`)
217
+
218
+ Use the `n` helper instead of `z` (Zod) in your content config. It provides CMS-aware field types that tell the editor which input to render, and accepts an options object that both validates data and configures the editor UI.
219
+
220
+ ```typescript
221
+ import { n } from '@nuasite/cms'
222
+ import { glob } from 'astro/loaders'
223
+ import { defineCollection, reference } from 'astro:content'
224
+
225
+ const tagsCollection = defineCollection({
226
+ loader: glob({ pattern: '**/*.json', base: 'src/content/tags' }),
227
+ schema: n.object({
228
+ name: n.string(),
229
+ }),
230
+ })
231
+
232
+ const blogCollection = defineCollection({
233
+ loader: glob({ pattern: '**/*.{md,mdx}', base: 'src/content/blog' }),
234
+ schema: n.object({
235
+ title: n.text({ placeholder: 'Enter title', maxLength: 120 }),
236
+ author: n.text(),
237
+ date: n.date().orderBy('desc'),
238
+ tags: n.array(reference('tags')),
239
+ excerpt: n.textarea({ rows: 2, maxLength: 300 }),
240
+ coverImage: n.image(),
241
+ featured: n.boolean().default(false),
242
+ }),
243
+ })
244
+ ```
245
+
246
+ All `n` methods return standard Zod schemas, so `.optional()`, `.nullable()`, `.default()`, and other Zod chainable methods work as usual.
247
+
248
+ ### Field Types
249
+
250
+ | Method | Editor input | Underlying Zod type |
251
+ | ----------------- | -------------- | --------------------------------- |
252
+ | `n.text()` | Text input | `z.string()` |
253
+ | `n.textarea()` | Multiline | `z.string()` |
254
+ | `n.number()` | Number input | `z.number()` |
255
+ | `n.boolean()` | Checkbox | `z.boolean()` |
256
+ | `n.image()` | Image picker | `z.string()` |
257
+ | `n.url()` | URL input | `z.string()` |
258
+ | `n.email()` | Email input | `z.string()` |
259
+ | `n.color()` | Color picker | `z.string()` |
260
+ | `n.date()` | Date picker | `z.string()` (coerces YAML dates) |
261
+ | `n.datetime()` | Datetime input | `z.string()` (coerces YAML dates) |
262
+ | `n.time()` | Time input | `z.string()` |
263
+ | `n.string()` | Auto-detected | `z.string()` (no CMS hint) |
264
+ | `n.object()` | — | `z.object()` |
265
+ | `n.array()` | — | `z.array()` |
266
+ | `n.enum()` | — | `z.enum()` |
267
+ | `n.coerce.date()` | — | `z.coerce.date()` |
268
+
269
+ ### Field Hints
270
+
271
+ Pass an options object to configure both Zod validation and editor input attributes in one place:
272
+
273
+ ```typescript
274
+ n.number({ min: 1, max: 100, step: 1 }) // <input type="number" min="1" max="100" step="1">
275
+ n.text({ placeholder: 'Enter title', maxLength: 120 })
276
+ n.textarea({ rows: 5, maxLength: 500, placeholder: '...' })
277
+ n.date({ min: '2024-01-01', max: '2030-12-31' })
278
+ n.image({ accept: 'image/png,image/jpeg' })
279
+ ```
280
+
281
+ | Field type | Available hints |
282
+ | -------------- | --------------------------------------- |
283
+ | `n.number()` | `min`, `max`, `step`, `placeholder` |
284
+ | `n.text()` | `placeholder`, `maxLength`, `minLength` |
285
+ | `n.textarea()` | `placeholder`, `maxLength`, `rows` |
286
+ | `n.url()` | `placeholder`, `maxLength`, `minLength` |
287
+ | `n.email()` | `placeholder`, `maxLength`, `minLength` |
288
+ | `n.date()` | `min`, `max` |
289
+ | `n.datetime()` | `min`, `max` |
290
+ | `n.time()` | `min`, `max` |
291
+ | `n.image()` | `accept` |
292
+
293
+ Numeric hints (`min`, `max`, `step`, `maxLength`, `minLength`) also apply Zod validation — out-of-range values will be rejected at content build time.
294
+
295
+ ### Collection Ordering
296
+
297
+ Chain `.orderBy()` on any scalar field to control entry order in the collections browser:
298
+
299
+ ```typescript
300
+ n.number({ min: 1, max: 100 }).orderBy('asc') // ascending (default)
301
+ n.date().orderBy('desc') // descending (newest first)
302
+ ```
303
+
304
+ The direction defaults to `'asc'` if omitted. Entries with a missing order field sort to the end.
305
+
216
306
  ## Component Operations
217
307
 
218
308
  Components in `componentDirs` (default: `src/components/`) are scanned for props and registered as insertable/removable elements. The editor can:
@@ -334,6 +424,16 @@ Deselects the currently selected component and closes the block editor. No addit
334
424
  // Default export
335
425
  import nuaCms from '@nuasite/cms'
336
426
 
427
+ // Schema helpers
428
+ import { n } from '@nuasite/cms'
429
+ import type {
430
+ DateHints,
431
+ ImageHints,
432
+ NumberHints,
433
+ TextareaHints,
434
+ TextHints,
435
+ } from '@nuasite/cms'
436
+
337
437
  // Media adapters
338
438
  import { contemberMedia, localMedia, s3Media } from '@nuasite/cms'
339
439
 
@@ -341,7 +441,10 @@ import { contemberMedia, localMedia, s3Media } from '@nuasite/cms'
341
441
  import type { MediaItem, MediaStorageAdapter } from '@nuasite/cms'
342
442
  import type {
343
443
  CmsManifest,
444
+ CollectionDefinition,
344
445
  ComponentDefinition,
446
+ FieldDefinition,
447
+ FieldHints,
345
448
  ManifestEntry,
346
449
  } from '@nuasite/cms'
347
450