@phtngyn/cms 0.1.0-beta.5 → 0.2.0-beta.2

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 (104) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +114 -41
  3. package/package.json +13 -1
  4. package/src/ApplicationTypes.d.ts +84 -0
  5. package/src/ApplicationTypes.js +1 -0
  6. package/src/{Configuration-CDMYfTre.js → Configuration-BU-UyGoH.js} +4 -4
  7. package/src/{Configuration-CDMYfTre.js.map → Configuration-BU-UyGoH.js.map} +1 -1
  8. package/src/Configuration.d.ts +43 -21
  9. package/src/Configuration.js.map +1 -1
  10. package/src/Content.d.ts +67 -0
  11. package/src/Content.js +291 -0
  12. package/src/Content.js.map +1 -0
  13. package/src/{ContentTransfer-C3Av9sWz.js → ContentTransfer-BhF9G56s.js} +8 -4
  14. package/src/ContentTransfer-BhF9G56s.js.map +1 -0
  15. package/src/ContentTransfer-CRQet3QJ.d.ts +48 -0
  16. package/src/{DocumentEngine-J7g-emdf.js → DocumentEngine-ClhFXW9M.js} +4 -4
  17. package/src/{DocumentEngine-J7g-emdf.js.map → DocumentEngine-ClhFXW9M.js.map} +1 -1
  18. package/src/DocumentModel.d.ts +14 -2
  19. package/src/DocumentModel.js +32 -4
  20. package/src/DocumentModel.js.map +1 -1
  21. package/src/{Forms-CfcI3tPU.js → Forms-BbkE7QfC.js} +47 -16
  22. package/src/Forms-BbkE7QfC.js.map +1 -0
  23. package/src/{Forms-Dj6NuW66.js → Forms-DoB56hfe.js} +24 -7
  24. package/src/Forms-DoB56hfe.js.map +1 -0
  25. package/src/{Forms-BsJMev5_.d.ts → Forms-R0HoTR-o.d.ts} +31 -13
  26. package/src/Forms.d.ts +23 -11
  27. package/src/Forms.js.map +1 -1
  28. package/src/I18n-zbqpWxki.d.ts +29 -0
  29. package/src/I18n.d.ts +2 -20
  30. package/src/Module.js +18 -7
  31. package/src/Module.js.map +1 -1
  32. package/src/{S3MediaObjects-CpPSzJto.js → S3MediaObjects-D5Ns4FM6.js} +61 -40
  33. package/src/S3MediaObjects-D5Ns4FM6.js.map +1 -0
  34. package/src/cli/content.js +63 -221
  35. package/src/cli/content.js.map +1 -1
  36. package/src/cli.js +120 -5
  37. package/src/cli.js.map +1 -1
  38. package/src/{connection-CBAy2qGy.js → connection-D5S9Iph-.js} +2 -2
  39. package/src/{connection-CBAy2qGy.js.map → connection-D5S9Iph-.js.map} +1 -1
  40. package/src/dev-lifecycle-CZ7iCzG9.js +515 -0
  41. package/src/dev-lifecycle-CZ7iCzG9.js.map +1 -0
  42. package/src/{installation-CNfIriVm.js → installation-BNuoRDOz.js} +601 -468
  43. package/src/installation-BNuoRDOz.js.map +1 -0
  44. package/src/internal/application-types.js +43 -0
  45. package/src/internal/application-types.js.map +1 -0
  46. package/src/internal/authoring-catalog.js +9 -4
  47. package/src/internal/authoring-catalog.js.map +1 -1
  48. package/src/internal/authoring-metadata.js +1 -1
  49. package/src/internal/configuration.js +149 -46
  50. package/src/internal/configuration.js.map +1 -1
  51. package/src/internal/dev-lifecycle.js +3 -0
  52. package/src/internal/diagnostics.js +47 -0
  53. package/src/internal/diagnostics.js.map +1 -0
  54. package/src/internal/document-model.js +2 -2
  55. package/src/internal/document-model.js.map +1 -1
  56. package/src/internal/form-capabilities.js +1 -1
  57. package/src/internal/i18n.js +27 -1
  58. package/src/internal/i18n.js.map +1 -1
  59. package/src/{location-BTqdfIzX.js → location-CPX7oj0o.js} +2 -2
  60. package/src/{location-BTqdfIzX.js.map → location-CPX7oj0o.js.map} +1 -1
  61. package/src/{models-B4IoTmp7.js → models-C9kF9JlQ.js} +2 -2
  62. package/src/{models-B4IoTmp7.js.map → models-C9kF9JlQ.js.map} +1 -1
  63. package/src/{models-Bah5B3bT.js → models-ChRxBChy.js} +9 -2
  64. package/src/models-ChRxBChy.js.map +1 -0
  65. package/src/runtime/delivery/context.js +1 -1
  66. package/src/runtime/delivery/plugin.js +28 -5
  67. package/src/runtime/delivery/plugin.js.map +1 -1
  68. package/src/runtime/delivery/preview.d.ts +1 -1
  69. package/src/runtime/delivery/types.d.ts +2 -85
  70. package/src/runtime/delivery/useCmsDocumentSeo.d.ts +8 -1
  71. package/src/runtime/delivery/useCmsDocumentSeo.js +19 -7
  72. package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -1
  73. package/src/runtime/delivery/useDocument.d.ts +2 -2
  74. package/src/runtime/delivery/useDocument.js.map +1 -1
  75. package/src/runtime/delivery/useDocumentQuery.d.ts +3 -3
  76. package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
  77. package/src/runtime/forms/document.js +1 -1
  78. package/src/runtime/forms/useCmsForm.js +89 -45
  79. package/src/runtime/forms/useCmsForm.js.map +1 -1
  80. package/src/runtime/i18n/useCmsI18n.d.ts +9 -7
  81. package/src/runtime/i18n/useCmsI18n.js +5 -4
  82. package/src/runtime/i18n/useCmsI18n.js.map +1 -1
  83. package/src/runtime/installation.js +2 -2
  84. package/src/runtime/plugin.js +12 -4
  85. package/src/runtime/plugin.js.map +1 -1
  86. package/src/runtime/preview/connection.js +1 -1
  87. package/src/runtime/preview/controller.js +2 -2
  88. package/src/runtime/preview/plugin.client.js +3 -3
  89. package/src/runtime/preview/store.js +1 -1
  90. package/src/runtime/sitemap.js +1 -1
  91. package/src/scalars-CA6kyipS.d.ts +7 -0
  92. package/src/{store-b2CSXgMt.js → store-B49J21NA.js} +3 -3
  93. package/src/{store-b2CSXgMt.js.map → store-B49J21NA.js.map} +1 -1
  94. package/src/types-DutFRtaG.d.ts +90 -0
  95. package/studio/{CreateDocumentDialog-GGey1uBc.js → CreateDocumentDialog-DJ8c0WZb.js} +3 -3
  96. package/studio/{documentPlanning-DRErYmBU.js → documentPlanning-CDb31mnl.js} +5935 -5869
  97. package/studio/host.css +1 -1
  98. package/studio/host.js +7608 -7250
  99. package/src/ContentTransfer-C3Av9sWz.js.map +0 -1
  100. package/src/Forms-CfcI3tPU.js.map +0 -1
  101. package/src/Forms-Dj6NuW66.js.map +0 -1
  102. package/src/S3MediaObjects-CpPSzJto.js.map +0 -1
  103. package/src/installation-CNfIriVm.js.map +0 -1
  104. package/src/models-Bah5B3bT.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0-beta.2
4
+
5
+ This beta replaces the earlier installation and form interfaces directly.
6
+
7
+ - Runtime installation: move database URLs, origins, authentication secrets, initialization tokens, and S3 connection values out of `cms.config.ts` and into Nuxt `runtimeConfig.cms` / `NUXT_CMS_*`. Keep only the PostgreSQL schema and S3 prefix under `installation`. Rebuild once; no database migration, content import, or reset is required.
8
+ - CLI: `cms dev` now loads `<root>/.env`, supports `--root`, `--env-file`, `--verbose`, and `--json`, and reports stable phase diagnostics. No data operation is required.
9
+ - Forms: handlers must return `accepted`, `invalid`, or `unavailable`; `void` is no longer accepted. The handler receives request metadata. Replace `field()` and nested controller refs with `control()` / `visibleControls` and plain reactive properties. Rebuild the application; no stored form migration is required.
10
+ - Generated types: Nuxt renderers should import named `CmsFormDefinition<'form-name'>` from `#cms/types` and use the auto-imported `useCmsForm`. Regenerate Nuxt types with `nuxt prepare` or a normal build.
11
+ - Collection fields: `field(schema, { default })` replaces routine `Schema.withDecodingDefaultKey(Effect.succeed(...))` plumbing. Existing advanced Effect schemas remain valid. No data operation is required.
12
+ - Locale metadata: `i18n.siteConfig` chooses `documents` (default) or `localized-routes`. Studio groups locale variants and can create blank or copied independent translations. No database migration or content rewrite is required.
13
+ - Studio density: Overview previews are visibility-gated and serialized; the form builder edits one selected step at a time. Rebuild to update the embedded Studio.
package/README.md CHANGED
@@ -24,7 +24,7 @@ export default defineNuxtConfig({
24
24
 
25
25
  ## Configure
26
26
 
27
- Create a PostgreSQL database and an S3-compatible bucket. The database user must be able to create and migrate CMS tables. Storage credentials need bucket access and object read, write, copy, list, and delete permissions. Configure public reads through your bucket or CDN and use that URL as `publicBaseUrl`; the module does not configure public access policies.
27
+ Create a PostgreSQL database and an S3-compatible bucket. The database user must be able to create and migrate CMS tables. Storage credentials need bucket access and object read, write, copy, list, and delete permissions. Configure public reads through your bucket or CDN; the module does not configure public access policies.
28
28
 
29
29
  Create `cms.config.ts` beside `nuxt.config.ts`:
30
30
 
@@ -32,17 +32,7 @@ Create `cms.config.ts` beside `nuxt.config.ts`:
32
32
  import { defineCmsConfig } from '@phtngyn/cms/Configuration'
33
33
  import { pageSchema } from './page.schema'
34
34
 
35
- function env(name: string): string {
36
- const value = process.env[name]
37
- if (!value) throw new Error(`Missing ${name}`)
38
- return value
39
- }
40
-
41
35
  export default defineCmsConfig({
42
- baseUrl: env('CMS_BASE_URL'), // Website origin, e.g. http://localhost:3000
43
- databaseUrl: env('DATABASE_URL'),
44
- secret: env('CMS_SECRET'),
45
- initializationToken: env('CMS_INITIALIZATION_TOKEN'),
46
36
  documents: {
47
37
  collections: {
48
38
  pages: {
@@ -53,26 +43,53 @@ export default defineCmsConfig({
53
43
  },
54
44
  },
55
45
  },
56
- media: {
57
- publicBaseUrl: env('S3_PUBLIC_URL'),
58
- storage: {
59
- bucket: env('S3_BUCKET'),
60
- region: env('S3_REGION'),
61
- credentials: {
62
- accessKeyId: env('S3_ACCESS_KEY_ID'),
63
- secretAccessKey: env('S3_SECRET_ACCESS_KEY'),
64
- },
65
- // For an S3-compatible provider, add its endpoint and, if required:
66
- // endpoint: env('S3_ENDPOINT'),
67
- // forcePathStyle: true,
46
+ installation: {
47
+ database: { schema: 'cms' },
48
+ media: {
49
+ prefix: 'cms/',
68
50
  },
69
51
  },
70
52
  })
71
53
  ```
72
54
 
73
- These environment variable names are examples; configuration is executable TypeScript, so you can use your own configuration source. Supply values during Nuxt development/build and to the deployed server. Generate distinct random values of at least 32 characters for `secret` and `initializationToken`, and keep them server-side. For example, run `node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"` separately for each.
55
+ Keep environment-specific values in Nuxt runtime configuration. The module supplies empty `runtimeConfig.cms` defaults, so a production build needs no credentials. Nitro reads these values only when the built server starts:
56
+
57
+ ```dotenv
58
+ NUXT_CMS_BASE_URL=http://localhost:3000
59
+ NUXT_CMS_DATABASE_URL=postgres://cms:cms@localhost:5432/cms
60
+ NUXT_CMS_INITIALIZATION_TOKEN=replace-with-a-distinct-32-character-random-token
61
+ NUXT_CMS_SECRET=replace-with-at-least-32-random-characters
62
+ NUXT_CMS_MEDIA_BUCKET=cms-media
63
+ NUXT_CMS_MEDIA_PUBLIC_BASE_URL=http://localhost:8333/cms-media
64
+ NUXT_CMS_MEDIA_REGION=us-east-1
65
+ NUXT_CMS_MEDIA_ENDPOINT=http://localhost:8333
66
+ NUXT_CMS_MEDIA_FORCE_PATH_STYLE=true
67
+ NUXT_CMS_MEDIA_ACCESS_KEY_ID=cms-media
68
+ NUXT_CMS_MEDIA_SECRET_ACCESS_KEY=cms-media-secret
69
+ ```
70
+
71
+ Generate distinct random values of at least 32 characters for the secret and initialization token, and keep them server-side. The same built artifact can be started against another environment by changing only these runtime values.
72
+
73
+ For storage using the AWS credential chain, omit both credential variables. Set `NUXT_CMS_MEDIA_PROVISION_BUCKET=true` to create a missing bucket automatically and grant bucket-creation permission. Set authored `installation.media: false` to build without uploads.
74
+
75
+ The PostgreSQL schema and S3 prefix are the installation's ownership boundary. CMS creates and migrates only the configured schema, and every media object stays under the configured prefix.
76
+
77
+ ## Local development lifecycle
78
+
79
+ Use the supported lifecycle before starting Nuxt:
80
+
81
+ ```sh
82
+ cms dev doctor
83
+ CMS_EMAIL=admin@example.test CMS_PASSWORD=admin@example.test cms dev bootstrap --content ./content --publish
84
+ cms dev reset --database --media
85
+ cms dev reset --database --media --yes --bootstrap --content ./content --publish
86
+ ```
87
+
88
+ All lifecycle commands accept `--root <directory>`, `--env-file <path>`, `--verbose`, and `--json`. Values already present in the process override the explicit environment file, which overrides `<root>/.env`, which overrides Nuxt runtime-config defaults.
74
89
 
75
- For storage using the AWS credential chain, omit `credentials`. To create a missing bucket automatically, set `provisionBucket: true` and grant bucket-creation permission. Set `media: false` to start without uploads.
90
+ `doctor` is read-only and reports pass, fail, or not-verified for configuration, PostgreSQL, migrations, S3, public media, packaged assets, readiness, and WebSockets. Add `--full` to validate pending migrations in a rolled-back transaction and probe database writes plus an actual publicly readable S3 object, or `--json` for editor and CI integrations. `bootstrap` prepares the Nuxt authoring catalog, provisions infrastructure, runs migrations, creates or updates the requested local administrator, and optionally plans and applies content without requiring a running server. Passwords come from `CMS_PASSWORD` or a private prompt.
91
+
92
+ `reset` is a dry run unless `--yes` is present. It refuses to run while the configured CMS is live and can remove only the configured PostgreSQL schema and S3 prefix. Choose `--database`, `--media`, or both.
76
93
 
77
94
  ## Content CLI
78
95
 
@@ -93,6 +110,30 @@ For a folder without `.cms-content.json`, the CLI reads nested `.md` and `.mdc`
93
110
 
94
111
  The command tree is intentionally shared: future CMS operations can be added beside `content` without separate executables.
95
112
 
113
+ The same workflow is available to Node scripts without parsing CLI output:
114
+
115
+ ```ts
116
+ import { createCmsContentClient, readCmsContentDirectory } from '@phtngyn/cms/Content'
117
+
118
+ const source = await readCmsContentDirectory('./content')
119
+ const client = createCmsContentClient({
120
+ baseUrl: 'http://localhost:3000',
121
+ credentials: {
122
+ email: process.env.CMS_EMAIL!,
123
+ password: process.env.CMS_PASSWORD!,
124
+ },
125
+ })
126
+
127
+ const plan = await client.plan(source, { overwrite: false, publish: true })
128
+
129
+ if (plan.valid) await client.apply(plan)
130
+
131
+ const document = await client.readDocument('8c62d2a7-b839-450a-bc08-5eccb420dc60')
132
+ await client.export({ directory: './content' })
133
+ ```
134
+
135
+ Plans expose creates, updates, publications, conflicts, issues, unchanged entries, and target-only sources. They are bound to their client, source, options, and generation; `apply(plan)` never replans. `readDocument(id)` returns the authored Markdown and exact publication state. `CmsContentError.code` provides stable authentication, not-found, conflict, validation, generation, filesystem, response, and network failure categories.
136
+
96
137
  ## Search and sharing
97
138
 
98
139
  The module installs the focused Nuxt robots, sitemap, schema.org, and SEO utility modules. Published CMS pages receive canonical, robots, social-sharing, and structured-data metadata automatically. The sitemap contains only published, indexable documents and uses their publication revision time for `lastmod`; localized variants are emitted as alternatives when they share a document key.
@@ -130,6 +171,24 @@ Keep raw robots directives and Nuxt module configuration in application code. Th
130
171
 
131
172
  Collections use **Standard Schema V1** for validation and **Standard JSON Schema V1** for the input structure Studio renders. This lets you keep your chosen validation library. Both contracts must be available on the collection schema; Standard Schema validation alone cannot describe controls. Studio requests draft 2020-12 JSON Schema input, resolves non-recursive local references, and runs the original validator when validating documents.
132
173
 
174
+ The public standards types come directly from `@standard-schema/spec`. For a validator whose JSON Schema adapter is separate, `defineCmsSchema()` combines both contracts and applies library-neutral presentation to nested field paths:
175
+
176
+ ```ts
177
+ import { defineCmsSchema } from '@phtngyn/cms/DocumentModel'
178
+
179
+ const pageSchema = defineCmsSchema({
180
+ validator: page,
181
+ jsonSchema: {
182
+ input: () => createInputSchema(page),
183
+ output: () => createOutputSchema(page),
184
+ },
185
+ fields: {
186
+ description: { label: 'Description', input: 'textarea' },
187
+ 'seo.description': { input: 'textarea' },
188
+ },
189
+ })
190
+ ```
191
+
133
192
  For a validator that already implements both standards, pass it directly as `schema`. Otherwise, adapt its validator and input/output JSON Schema converters:
134
193
 
135
194
  ```ts
@@ -149,19 +208,17 @@ Supply actual draft 2020-12 schemas from the chosen library, using its input and
149
208
  Effect schemas are also accepted directly. The CMS derives Studio inputs from their encoded structure and runs the original decoder, preserving defaults, transformations, and asynchronous validation. Decoders must not require external Effect services, and their output must be a JSON-compatible object.
150
209
 
151
210
  ```ts
152
- import { Effect, Schema } from 'effect'
211
+ import { Schema } from 'effect'
153
212
  import { CalendarDate, field } from '@phtngyn/cms/DocumentModel'
154
213
 
155
214
  const pageSchema = Schema.Struct({
156
- description: field(Schema.String.annotate({ default: '' }), { input: 'textarea' }).pipe(
157
- Schema.withDecodingDefaultKey(Effect.succeed('')),
158
- ),
215
+ description: field(Schema.String, { default: '', input: 'textarea' }),
159
216
  publishedAt: Schema.optionalKey(field(CalendarDate, { label: 'Published on' })),
160
217
  title: Schema.NonEmptyString,
161
218
  })
162
219
  ```
163
220
 
164
- Use `Schema.optionalKey` for optional fields and `Schema.Finite` for numeric fields. An encoded `default` annotation supplies Studio's initial value; `Schema.withDecodingDefaultKey` applies that default during validation. `CalendarDate` validates calendar dates stored as `YYYY-MM-DD` strings. `field` adds presentation annotations to the encoded side of an Effect schema.
221
+ Use `Schema.optionalKey` for optional fields and `Schema.Finite` for numeric fields. A `field` default supplies Studio's initial value and applies only when the containing key is absent; invalid explicit values still fail. `CalendarDate` validates calendar dates stored as `YYYY-MM-DD` strings. Advanced Effect schemas, transformations, and defaults remain supported directly. `effect` is an intentional runtime dependency and is installed explicitly because authored schemas expose its public types.
165
222
 
166
223
  Studio supports strings, calendar dates, finite numbers, booleans, string arrays, choices, and nested objects. Non-recursive local references are resolved. Ambiguous unions, structural intersections, recursive schemas, and unsupported field shapes fail configuration rather than producing incomplete forms.
167
224
 
@@ -186,6 +243,20 @@ Draft delivery requires the current Studio session and document access permissio
186
243
  - Import `defineCmsBlock` from `@phtngyn/cms/Authoring` to declare editable props and slots in your Nuxt Vue components.
187
244
  - Import Markdown folders with `cms content plan` and `cms content apply` after creating the first administrator.
188
245
 
246
+ Nuxt generates `#cms/types` whenever configuration, collection schemas, or authored blocks change. It includes collection, form, block, locale, message-key, and interpolation-parameter types. Collection schema outputs flow through `useDocument<'pages'>()` and `useDocumentQuery()`, including typed filter and sort paths. Registered form names and configured capabilities narrow `CmsFormDefinition<'contact-form'>`.
247
+
248
+ ```ts
249
+ import type { CmsCollection, CmsFormDefinition, CmsLocale } from '#cms/types'
250
+
251
+ const page: CmsCollection<'pages'> | null = useDocument<'pages'>('/').data.value
252
+ const locale: CmsLocale = 'de'
253
+ const form = {} as CmsFormDefinition<'contact-form'>
254
+ ```
255
+
256
+ `useCmsI18n().t()` autocompletes configured keys and requires the exact placeholders in each message. Nuxt preparation rejects extra locale keys and inconsistent placeholders. Missing translations are allowed only when the configured fallback locale supplies the canonical message.
257
+
258
+ CMS also synchronizes owned document routes with Nuxt Site Config. The delivered document locale wins, the route prefix applies while delivery resolves, and non-CMS routes retain the application's Site Config. Set `i18n.siteConfig: 'localized-routes'` to give CMS-derived language metadata to custom localized routes too; document canonical URLs, alternates, and SEO remain document-only. The default `documents` scope avoids conflicts with another application i18n module.
259
+
189
260
  ## Studio and authoring during development
190
261
 
191
262
  Studio at `/_cms/app` is a separate HTML document, Vue application, and Tailwind build. It owns its Nuxt UI settings, styles, and overlays independently of the website. Your app root and layouts apply only to the website. Both applications use the same Nuxt server; no separate Studio server or port is required.
@@ -267,9 +338,10 @@ forms: {
267
338
  fieldTypes: ['text', 'email', 'textarea', 'number', 'checkbox', 'select'],
268
339
  rules: ['required', 'minLength', 'maxLength', 'min', 'max'],
269
340
  }),
270
- onSubmit: async ({ form, values }) => {
341
+ onSubmit: async ({ form, request, values }) => {
271
342
  // Server only: deliver or persist validated answers here.
272
343
  // values are keyed by stable question IDs; form.steps contains their labels.
344
+ return { status: 'accepted', reference: request.id }
273
345
  },
274
346
  },
275
347
  },
@@ -280,24 +352,25 @@ Create `ContactForm.vue` with an optional `cmsForm?: CmsFormDefinition` prop. Th
280
352
  A valid authored form receives `cmsForm` from `DocumentRenderer`. Incomplete drafts do not; show a helpful placeholder until the form is valid. Mount your custom form contents only when that definition exists:
281
353
 
282
354
  ```ts
283
- import type { CmsFormDefinition } from '@phtngyn/cms/Forms'
284
- import { useCmsForm } from '@phtngyn/cms/Forms'
355
+ import type { CmsFormDefinition } from '#cms/types'
285
356
 
286
- const props = defineProps<{ definition: CmsFormDefinition }>()
357
+ const props = defineProps<{ definition: CmsFormDefinition<'contact-form'> }>()
287
358
  const {
288
- fields,
289
359
  steps,
290
- values,
291
360
  errors,
292
361
  currentStep,
293
362
  stepIndex,
294
363
  isFirstStep,
295
364
  isLastStep,
296
- isSubmitting,
365
+ submitting,
297
366
  isSuccess,
298
367
  isPreview,
299
- error,
300
- fieldState,
368
+ submitError,
369
+ canSubmit,
370
+ visibleControls,
371
+ invalidControls,
372
+ firstInvalidControlId,
373
+ control,
301
374
  next,
302
375
  previous,
303
376
  validate,
@@ -306,13 +379,13 @@ const {
306
379
  } = useCmsForm(() => props.definition)
307
380
  ```
308
381
 
309
- The composable accepts a value, ref, or getter and returns destructurable refs and functions. Use `fieldState(field)` for each visible question. It returns the current `value`, `error`, normalized `setValue(input)`, and `inputAttrs` with an ID, name, required state, and ARIA error/help references. Render help with `id=state.helpId` and errors with `id=state.errorId` so those references have matching elements. For example, a native control can use `v-bind="state.inputAttrs"`, `:value="state.value"`, and `@input="state.setValue($event.target.value)"`. Checkbox controls should pass their checked boolean. Valid number answers become numbers, empty answers become null, and invalid number text remains visible to validation. Empty optional answers reach the server handler as null. `values` and `errors` remain available for custom renderers; `_form` holds a form-wide error. Render accessible labels, error announcements, progress, and focus behavior in your application.
382
+ The composable accepts a value, ref, or getter and returns destructurable refs and functions. Iterate `visibleControls`; each reactive control exposes plain template properties: `field`, writable `value`, `dirty`, `touched`, `valid`, `error`, `attrs`, `helpAttrs`, `errorAttrs`, and `touch()`. Bind `attrs` to the native control. CMS keeps stable IDs and ARIA relationships aligned without nested `.value` access inside a `v-for`. Valid number answers become numbers, empty answers become null, and invalid number text remains visible to validation. `canSubmit`, `invalidControls`, and `firstInvalidControlId` centralize form-level behavior, while `submitError` and `errors._form` cover transport and server failures.
310
383
 
311
384
  `next()` validates the current step, `previous()` preserves answers, and `submit()` validates every step and returns whether submission succeeded. A required checkbox must be checked.
312
385
 
313
386
  Definition replacements preserve answers for matching IDs and field types within the same form, clear validation state, and cancel pending client requests. `reset()` clears answers and submission state. The composable prevents overlapping submissions and repeat submission after success until reset. Scope disposal cancels client work. Cancellation cannot undo a server handler that has already started. An optional `submit(payload, signal)` transport override supports advanced integrations; it must return `CmsFormResult` and preserve server-side validation.
314
387
 
315
- The endpoint resolves the current published form and checks its exact serialized revision, including capabilities and question settings. Stale pages must reload. Draft preview validates locally without invoking the handler. Invalid structure prevents publication; invalid answers and unknown answer keys never reach the handler. Dynamic editor-created questions have runtime-validated answer types, not compile-time-known answer keys.
388
+ The endpoint resolves the current published form and checks its exact serialized revision, including capabilities and question settings. Stale pages must reload. Draft preview validates locally without invoking the handler. Invalid structure prevents publication; invalid answers and unknown answer keys never reach the handler. The application handler explicitly returns `accepted`, `invalid`, or `unavailable`; trusted validation errors may target current field IDs or `_form`. Unexpected failures are correlated by request ID in server logs without recording answers and return only a generic public failure.
316
389
 
317
390
  Requests are limited to 128 KiB and 60 submissions per minute per server process, shared across forms. Browser requests with a foreign Origin are rejected. For deployments with multiple replicas or stronger spam protection, enforce a shared limit or challenge at your edge. There is no automatic retry, durable deduplication, inbox, email delivery, upload, branching, or save-and-resume in this release. Application handlers should account for retries after an uncertain network outcome. Configuration changes use Nuxt's restart lifecycle.
318
391
 
package/package.json CHANGED
@@ -17,13 +17,14 @@
17
17
  "directory": "packages/nuxt"
18
18
  },
19
19
  "name": "@phtngyn/cms",
20
- "version": "0.1.0-beta.5",
20
+ "version": "0.2.0-beta.2",
21
21
  "type": "module",
22
22
  "files": [
23
23
  "bin",
24
24
  "studio",
25
25
  "src",
26
26
  "README.md",
27
+ "CHANGELOG.md",
27
28
  "LICENSE"
28
29
  ],
29
30
  "exports": {
@@ -43,6 +44,10 @@
43
44
  "types": "./src/DocumentModel.d.ts",
44
45
  "import": "./src/DocumentModel.js"
45
46
  },
47
+ "./ApplicationTypes": {
48
+ "types": "./src/ApplicationTypes.d.ts",
49
+ "import": "./src/ApplicationTypes.js"
50
+ },
46
51
  "./I18n": {
47
52
  "types": "./src/I18n.d.ts",
48
53
  "import": "./src/I18n.js"
@@ -50,6 +55,10 @@
50
55
  "./Forms": {
51
56
  "types": "./src/Forms.d.ts",
52
57
  "import": "./src/Forms.js"
58
+ },
59
+ "./Content": {
60
+ "types": "./src/Content.d.ts",
61
+ "import": "./src/Content.js"
53
62
  }
54
63
  },
55
64
  "dependencies": {
@@ -63,11 +72,13 @@
63
72
  "@nuxt/schema": "^4.5.2",
64
73
  "@nuxtjs/robots": "^6.2.2",
65
74
  "@nuxtjs/sitemap": "^8.5.1",
75
+ "@standard-schema/spec": "^1.1.0",
66
76
  "@tiptap/core": "^3.31.3",
67
77
  "@tiptap/starter-kit": "^3.31.3",
68
78
  "@tiptap/y-tiptap": "^3.0.9",
69
79
  "@vue/compiler-sfc": "^3.6.0-rc.8",
70
80
  "better-auth": "^1.7.4",
81
+ "c12": "^3.3.4",
71
82
  "comark": "^0.7.0",
72
83
  "diff": "^9.0.0",
73
84
  "drizzle-orm": "^1.0.0-rc.5-169397b",
@@ -77,6 +88,7 @@
77
88
  "nitropack": "^2.13.4",
78
89
  "nuxt-schema-org": "^6.3.1",
79
90
  "nuxt-seo-utils": "^8.5.0",
91
+ "nuxt-site-config": "^4.2.3",
80
92
  "pg": "^8.23.0",
81
93
  "typescript": "npm:typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2",
82
94
  "vite": "^8.3.0",
@@ -0,0 +1,84 @@
1
+ import { a as FormStep, n as FormDefinition, r as FormField } from "./Forms-R0HoTR-o.js";
2
+ import { a as CmsMessageParameters, i as CmsMessageKey, r as CmsLocale } from "./I18n-zbqpWxki.js";
3
+ import { n as DeliveryDocumentPage, t as DeliveryDocument } from "./types-DutFRtaG.js";
4
+ import { Schema } from "effect";
5
+ import { StandardSchemaV1 } from "@standard-schema/spec";
6
+ //#region src/ApplicationTypes.d.ts
7
+ /** Nuxt augments this registry from the authored CMS configuration and component catalog. */
8
+ export interface CmsApplicationRegistry {}
9
+ type AuthoredConfiguration = CmsApplicationRegistry extends {
10
+ configuration: infer Configuration;
11
+ } ? Configuration : never;
12
+ type AuthoredCollections = AuthoredConfiguration extends {
13
+ documents: {
14
+ collections: infer Collections;
15
+ };
16
+ } ? Collections : Record<string, never>;
17
+ type AuthoredForms = AuthoredConfiguration extends {
18
+ forms: infer Forms;
19
+ } ? NonNullable<Forms> : Record<string, never>;
20
+ type CollectionSchema<Name extends keyof AuthoredCollections> = AuthoredCollections[Name] extends {
21
+ schema: infer CollectionSchema;
22
+ } ? CollectionSchema : never;
23
+ type SchemaOutput<Value> = Value extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Value> : Value extends Schema.Top ? Value['Type'] : never;
24
+ type JsonOutput<Value> = Value extends object ? Value : Record<string, never>;
25
+ export type CmsCollectionName = Extract<keyof AuthoredCollections, string>;
26
+ export type CmsFormName = Extract<keyof AuthoredForms, string>;
27
+ export type CmsBlockName = CmsApplicationRegistry extends {
28
+ blockName: infer Name extends string;
29
+ } ? Name : string;
30
+ export type CmsCollection<Name extends CmsCollectionName> = Omit<DeliveryDocument, 'body' | 'collection' | 'data' | 'locale'> & {
31
+ readonly body: Omit<DeliveryDocument['body'], 'frontmatter'> & {
32
+ readonly frontmatter: JsonOutput<SchemaOutput<CollectionSchema<Name>>>;
33
+ };
34
+ readonly collection: Name;
35
+ readonly data: JsonOutput<SchemaOutput<CollectionSchema<Name>>>;
36
+ readonly locale?: CmsLocale;
37
+ };
38
+ type FormCapabilities<Name extends CmsFormName> = AuthoredForms[Name] extends {
39
+ definition: infer Capabilities;
40
+ } ? Capabilities : never;
41
+ type FormFieldType<Name extends CmsFormName> = FormCapabilities<Name> extends {
42
+ fieldTypes: ReadonlyArray<infer FieldType>;
43
+ } ? Extract<FieldType, FormField['type']> : never;
44
+ type FormRuleProperty<Rule> = Rule extends 'required' ? 'required' : Rule extends 'minLength' ? 'minLength' : Rule extends 'maxLength' ? 'maxLength' : Rule extends 'min' ? 'min' : Rule extends 'max' ? 'max' : never;
45
+ type AllowedFormRuleProperty<Name extends CmsFormName> = FormRuleProperty<FormCapabilities<Name> extends {
46
+ rules: ReadonlyArray<infer Rule>;
47
+ } ? Rule : never>;
48
+ type RestrictFormRules<Field, Name extends CmsFormName> = Field extends FormField ? Omit<Field, Exclude<'max' | 'maxLength' | 'min' | 'minLength' | 'required', AllowedFormRuleProperty<Name>>> : never;
49
+ type CmsFormField<Name extends CmsFormName> = RestrictFormRules<Extract<FormField, {
50
+ readonly type: FormFieldType<Name>;
51
+ }>, Name>;
52
+ type CmsFormStep<Name extends CmsFormName> = Omit<FormStep, 'fields'> & {
53
+ readonly fields: ReadonlyArray<CmsFormField<Name>>;
54
+ };
55
+ export type CmsFormDefinition<Name extends CmsFormName = CmsFormName> = Omit<FormDefinition, 'name' | 'steps'> & {
56
+ readonly name: Name;
57
+ readonly steps: ReadonlyArray<CmsFormStep<Name>>;
58
+ };
59
+ type Scalar = boolean | number | string | null;
60
+ type FieldPaths<Value> = Value extends ReadonlyArray<unknown> ? never : Value extends object ? { [Key in Extract<keyof Value, string>]: NonNullable<Value[Key]> extends Scalar | ReadonlyArray<Scalar> ? Key : `${Key}.${FieldPaths<NonNullable<Value[Key]>>}`; }[Extract<keyof Value, string>] : never;
61
+ type CollectionDataField<Name extends CmsCollectionName> = Extract<FieldPaths<CmsCollection<Name>['data']>, string>;
62
+ type FieldValue<Value, Path extends string> = Path extends `${infer Head}.${infer Tail}` ? Head extends keyof Value ? FieldValue<NonNullable<Value[Head]>, Tail> : never : Path extends keyof Value ? Value[Path] : never;
63
+ type DocumentField<Name extends CmsCollectionName> = 'collection' | 'description' | 'id' | 'key' | 'locale' | 'path' | 'source.path' | 'title' | CollectionDataField<Name> | `data.${CollectionDataField<Name>}`;
64
+ type DocumentFieldValue<Name extends CmsCollectionName, Field extends DocumentField<Name>> = Field extends `data.${infer Path}` ? FieldValue<CmsCollection<Name>['data'], Path> : Field extends CollectionDataField<Name> ? FieldValue<CmsCollection<Name>['data'], Field> : Field extends 'collection' ? Name : Field extends 'locale' ? CmsLocale : Field extends 'source.path' ? CmsCollection<Name>['source']['path'] : Field extends keyof CmsCollection<Name> ? CmsCollection<Name>[Field] : never;
65
+ type FilterValue<Value> = NonNullable<Value> extends ReadonlyArray<infer Item> ? Extract<NonNullable<Item>, Scalar> | ReadonlyArray<Extract<NonNullable<Item>, Scalar>> : Extract<NonNullable<Value>, Scalar> | ReadonlyArray<Extract<NonNullable<Value>, Scalar>>;
66
+ type CmsDocumentQueryWhere<Name extends CmsCollectionName> = { readonly [Field in DocumentField<Name>]?: FilterValue<DocumentFieldValue<Name, Field>>; };
67
+ export interface CmsDocumentQuerySort<Name extends CmsCollectionName> {
68
+ readonly direction?: 'asc' | 'desc';
69
+ readonly field: DocumentField<Name>;
70
+ }
71
+ export interface CmsDocumentQuery<Name extends CmsCollectionName = CmsCollectionName> {
72
+ readonly collection?: Name;
73
+ readonly locale?: CmsLocale;
74
+ readonly limit: number;
75
+ readonly offset?: number;
76
+ readonly sort?: CmsDocumentQuerySort<Name> | ReadonlyArray<CmsDocumentQuerySort<Name>>;
77
+ readonly where?: CmsDocumentQueryWhere<Name>;
78
+ }
79
+ export interface CmsDocumentPage<Name extends CmsCollectionName> extends Omit<DeliveryDocumentPage, 'items'> {
80
+ readonly items: ReadonlyArray<CmsCollection<Name>>;
81
+ }
82
+ //#endregion
83
+ export type { CmsLocale, CmsMessageKey, CmsMessageParameters };
84
+ //# sourceMappingURL=ApplicationTypes.d.ts.map
@@ -0,0 +1 @@
1
+ export { };
@@ -1,6 +1,6 @@
1
- import { r as FormCapabilities } from "./Forms-CfcI3tPU.js";
2
- import { c as resolveCmsComarkAttributes, d as createCmsComarkRenderData, l as withCmsComarkProps, r as formStructureIssues, s as projectCmsEditorComponentAttributes } from "./Forms-Dj6NuW66.js";
3
- import { Ct as CollectionName, St as AddressPattern, Tt as SourceDirectory, b as MarkdownNode, n as CmsMarkdownDocument, wt as Locale } from "./models-Bah5B3bT.js";
1
+ import { Ct as CollectionName, St as AddressPattern, Tt as SourceDirectory, b as MarkdownNode, n as CmsMarkdownDocument, wt as Locale } from "./models-ChRxBChy.js";
2
+ import { r as FormCapabilities } from "./Forms-BbkE7QfC.js";
3
+ import { c as resolveCmsComarkAttributes, d as createCmsComarkRenderData, l as withCmsComarkProps, r as formStructureIssues, s as projectCmsEditorComponentAttributes } from "./Forms-DoB56hfe.js";
4
4
  import { a as createEditorNodeId, n as editorMetadata, o as editorNodeId } from "./metadata-BEZPWh-3.js";
5
5
  import { Effect, Option, Predicate, Schema } from "effect";
6
6
  import { Extension } from "@tiptap/core";
@@ -1030,4 +1030,4 @@ const MediaConfiguration = Schema.Struct({
1030
1030
 
1031
1031
  //#endregion
1032
1032
  export { finiteNumberAttribute as C, compactJsonAttributes as S, comarkTagForEditorMark as _, BlockAuthoring as a, encodeEditorImageAttributes as b, parseAuthoringContent as c, EditorRenderFailed as d, DOCUMENT_ENGINE_FINGERPRINT as f, EDITOR_MARK_TYPES as g, comarkToEditor as h, MediaConfiguration as i, validateCollectionBody as l, SOURCE_MAPPINGS_META_KEY as m, ComponentDefinition as n, createAuthoringDocumentValidator as o, createDocumentParser as p, DocumentConfiguration as r, createAuthoringSchema as s, AuthoringCatalog as t, EditorDocumentInvalid as u, isEditorMark as v, nonEmptyStringAttribute as w, normalizeEditorImageLayout as x, EDITOR_IMAGE_LAYOUT_ATTRIBUTE as y };
1033
- //# sourceMappingURL=Configuration-CDMYfTre.js.map
1033
+ //# sourceMappingURL=Configuration-BU-UyGoH.js.map