@elytracms/core 0.0.6
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 +21 -0
- package/dist/clipboard/build.d.ts +77 -0
- package/dist/clipboard/build.js +128 -0
- package/dist/clipboard/build.js.map +1 -0
- package/dist/clipboard/clipboard.d.ts +33 -0
- package/dist/clipboard/clipboard.js +45 -0
- package/dist/clipboard/clipboard.js.map +1 -0
- package/dist/clipboard/fragment.d.ts +112 -0
- package/dist/clipboard/fragment.js +79 -0
- package/dist/clipboard/fragment.js.map +1 -0
- package/dist/clipboard/html.d.ts +26 -0
- package/dist/clipboard/html.js +82 -0
- package/dist/clipboard/html.js.map +1 -0
- package/dist/clipboard/index.d.ts +15 -0
- package/dist/clipboard/index.js +16 -0
- package/dist/clipboard/index.js.map +1 -0
- package/dist/clipboard/references.d.ts +50 -0
- package/dist/clipboard/references.js +0 -0
- package/dist/clipboard/references.js.map +1 -0
- package/dist/clipboard/serialize.d.ts +69 -0
- package/dist/clipboard/serialize.js +130 -0
- package/dist/clipboard/serialize.js.map +1 -0
- package/dist/cms-core/collections.d.ts +82 -0
- package/dist/cms-core/collections.js +187 -0
- package/dist/cms-core/collections.js.map +1 -0
- package/dist/cms-core/documents.d.ts +71 -0
- package/dist/cms-core/documents.js +67 -0
- package/dist/cms-core/documents.js.map +1 -0
- package/dist/cms-core/envelopes.d.ts +80 -0
- package/dist/cms-core/envelopes.js +124 -0
- package/dist/cms-core/envelopes.js.map +1 -0
- package/dist/cms-core/fields.d.ts +220 -0
- package/dist/cms-core/fields.js +250 -0
- package/dist/cms-core/fields.js.map +1 -0
- package/dist/cms-core/fixtures.d.ts +92 -0
- package/dist/cms-core/fixtures.js +357 -0
- package/dist/cms-core/fixtures.js.map +1 -0
- package/dist/cms-core/hierarchy.d.ts +113 -0
- package/dist/cms-core/hierarchy.js +223 -0
- package/dist/cms-core/hierarchy.js.map +1 -0
- package/dist/cms-core/index.d.ts +25 -0
- package/dist/cms-core/index.js +26 -0
- package/dist/cms-core/index.js.map +1 -0
- package/dist/cms-core/infer.d.ts +103 -0
- package/dist/cms-core/infer.js +57 -0
- package/dist/cms-core/infer.js.map +1 -0
- package/dist/cms-core/issues.d.ts +92 -0
- package/dist/cms-core/issues.js +74 -0
- package/dist/cms-core/issues.js.map +1 -0
- package/dist/cms-core/json-schema.d.ts +25 -0
- package/dist/cms-core/json-schema.js +110 -0
- package/dist/cms-core/json-schema.js.map +1 -0
- package/dist/cms-core/localization.d.ts +51 -0
- package/dist/cms-core/localization.js +89 -0
- package/dist/cms-core/localization.js.map +1 -0
- package/dist/cms-core/routes.d.ts +76 -0
- package/dist/cms-core/routes.js +220 -0
- package/dist/cms-core/routes.js.map +1 -0
- package/dist/cms-core/self-type.d.ts +41 -0
- package/dist/cms-core/self-type.js +191 -0
- package/dist/cms-core/self-type.js.map +1 -0
- package/dist/cms-core/url-for-document.d.ts +39 -0
- package/dist/cms-core/url-for-document.js +138 -0
- package/dist/cms-core/url-for-document.js.map +1 -0
- package/dist/cms-core/validate-document.d.ts +121 -0
- package/dist/cms-core/validate-document.js +871 -0
- package/dist/cms-core/validate-document.js.map +1 -0
- package/dist/cms-core/versions.d.ts +75 -0
- package/dist/cms-core/versions.js +97 -0
- package/dist/cms-core/versions.js.map +1 -0
- package/dist/collaboration/approval.d.ts +68 -0
- package/dist/collaboration/approval.js +104 -0
- package/dist/collaboration/approval.js.map +1 -0
- package/dist/collaboration/collaboration.d.ts +49 -0
- package/dist/collaboration/collaboration.js +56 -0
- package/dist/collaboration/collaboration.js.map +1 -0
- package/dist/collaboration/comments.d.ts +72 -0
- package/dist/collaboration/comments.js +118 -0
- package/dist/collaboration/comments.js.map +1 -0
- package/dist/collaboration/core.d.ts +25 -0
- package/dist/collaboration/core.js +26 -0
- package/dist/collaboration/core.js.map +1 -0
- package/dist/collaboration/index.d.ts +14 -0
- package/dist/collaboration/index.js +15 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/presence.d.ts +62 -0
- package/dist/collaboration/presence.js +85 -0
- package/dist/collaboration/presence.js.map +1 -0
- package/dist/collaboration/publishing.d.ts +60 -0
- package/dist/collaboration/publishing.js +93 -0
- package/dist/collaboration/publishing.js.map +1 -0
- package/dist/collaboration/versions.d.ts +52 -0
- package/dist/collaboration/versions.js +81 -0
- package/dist/collaboration/versions.js.map +1 -0
- package/dist/component-registry/index.d.ts +3 -0
- package/dist/component-registry/index.js +4 -0
- package/dist/component-registry/index.js.map +1 -0
- package/dist/component-registry/issues.d.ts +6 -0
- package/dist/component-registry/issues.js +2 -0
- package/dist/component-registry/issues.js.map +1 -0
- package/dist/component-registry/manifest.d.ts +164 -0
- package/dist/component-registry/manifest.js +129 -0
- package/dist/component-registry/manifest.js.map +1 -0
- package/dist/component-registry/registry.d.ts +33 -0
- package/dist/component-registry/registry.js +90 -0
- package/dist/component-registry/registry.js.map +1 -0
- package/dist/content/__fixtures__/filterable-collections.d.ts +14 -0
- package/dist/content/__fixtures__/filterable-collections.js +15 -0
- package/dist/content/__fixtures__/filterable-collections.js.map +1 -0
- package/dist/content/__fixtures__/sample-accessor-types.d.ts +56 -0
- package/dist/content/__fixtures__/sample-accessor-types.js +5 -0
- package/dist/content/__fixtures__/sample-accessor-types.js.map +1 -0
- package/dist/content/__fixtures__/sample-delivery-types.d.ts +122 -0
- package/dist/content/__fixtures__/sample-delivery-types.js +5 -0
- package/dist/content/__fixtures__/sample-delivery-types.js.map +1 -0
- package/dist/content/assets.d.ts +53 -0
- package/dist/content/assets.js +38 -0
- package/dist/content/assets.js.map +1 -0
- package/dist/content/binding-sources.d.ts +53 -0
- package/dist/content/binding-sources.js +73 -0
- package/dist/content/binding-sources.js.map +1 -0
- package/dist/content/client.d.ts +90 -0
- package/dist/content/client.js +383 -0
- package/dist/content/client.js.map +1 -0
- package/dist/content/codegen.d.ts +54 -0
- package/dist/content/codegen.js +305 -0
- package/dist/content/codegen.js.map +1 -0
- package/dist/content/context.d.ts +38 -0
- package/dist/content/context.js +21 -0
- package/dist/content/context.js.map +1 -0
- package/dist/content/cursor.d.ts +33 -0
- package/dist/content/cursor.js +104 -0
- package/dist/content/cursor.js.map +1 -0
- package/dist/content/index.d.ts +28 -0
- package/dist/content/index.js +29 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/locale.d.ts +30 -0
- package/dist/content/locale.js +26 -0
- package/dist/content/locale.js.map +1 -0
- package/dist/content/perspective.d.ts +29 -0
- package/dist/content/perspective.js +31 -0
- package/dist/content/perspective.js.map +1 -0
- package/dist/content/populate.d.ts +25 -0
- package/dist/content/populate.js +22 -0
- package/dist/content/populate.js.map +1 -0
- package/dist/content/query.d.ts +122 -0
- package/dist/content/query.js +257 -0
- package/dist/content/query.js.map +1 -0
- package/dist/content/raw.d.ts +13 -0
- package/dist/content/raw.js +14 -0
- package/dist/content/raw.js.map +1 -0
- package/dist/content/resolve.d.ts +97 -0
- package/dist/content/resolve.js +261 -0
- package/dist/content/resolve.js.map +1 -0
- package/dist/content/serialize.d.ts +30 -0
- package/dist/content/serialize.js +57 -0
- package/dist/content/serialize.js.map +1 -0
- package/dist/content/tags.d.ts +54 -0
- package/dist/content/tags.js +40 -0
- package/dist/content/tags.js.map +1 -0
- package/dist/data-binding/fixtures.d.ts +20 -0
- package/dist/data-binding/fixtures.js +47 -0
- package/dist/data-binding/fixtures.js.map +1 -0
- package/dist/data-binding/index.d.ts +14 -0
- package/dist/data-binding/index.js +15 -0
- package/dist/data-binding/index.js.map +1 -0
- package/dist/data-binding/issues.d.ts +45 -0
- package/dist/data-binding/issues.js +46 -0
- package/dist/data-binding/issues.js.map +1 -0
- package/dist/data-binding/resolve.d.ts +87 -0
- package/dist/data-binding/resolve.js +204 -0
- package/dist/data-binding/resolve.js.map +1 -0
- package/dist/data-binding/sample.d.ts +21 -0
- package/dist/data-binding/sample.js +23 -0
- package/dist/data-binding/sample.js.map +1 -0
- package/dist/data-binding/sources.d.ts +225 -0
- package/dist/data-binding/sources.js +154 -0
- package/dist/data-binding/sources.js.map +1 -0
- package/dist/data-binding/tokens.d.ts +62 -0
- package/dist/data-binding/tokens.js +150 -0
- package/dist/data-binding/tokens.js.map +1 -0
- package/dist/design-tokens/css.d.ts +34 -0
- package/dist/design-tokens/css.js +51 -0
- package/dist/design-tokens/css.js.map +1 -0
- package/dist/design-tokens/index.d.ts +10 -0
- package/dist/design-tokens/index.js +11 -0
- package/dist/design-tokens/index.js.map +1 -0
- package/dist/design-tokens/style-guide.d.ts +30 -0
- package/dist/design-tokens/style-guide.js +31 -0
- package/dist/design-tokens/style-guide.js.map +1 -0
- package/dist/design-tokens/tokens.d.ts +89 -0
- package/dist/design-tokens/tokens.js +112 -0
- package/dist/design-tokens/tokens.js.map +1 -0
- package/dist/export-sync/builder-source.d.ts +85 -0
- package/dist/export-sync/builder-source.js +124 -0
- package/dist/export-sync/builder-source.js.map +1 -0
- package/dist/export-sync/check.d.ts +61 -0
- package/dist/export-sync/check.js +126 -0
- package/dist/export-sync/check.js.map +1 -0
- package/dist/export-sync/cli.d.ts +89 -0
- package/dist/export-sync/cli.js +323 -0
- package/dist/export-sync/cli.js.map +1 -0
- package/dist/export-sync/core.d.ts +58 -0
- package/dist/export-sync/core.js +41 -0
- package/dist/export-sync/core.js.map +1 -0
- package/dist/export-sync/eject.d.ts +28 -0
- package/dist/export-sync/eject.js +21 -0
- package/dist/export-sync/eject.js.map +1 -0
- package/dist/export-sync/fixtures.d.ts +25 -0
- package/dist/export-sync/fixtures.js +87 -0
- package/dist/export-sync/fixtures.js.map +1 -0
- package/dist/export-sync/generate.d.ts +7 -0
- package/dist/export-sync/generate.js +505 -0
- package/dist/export-sync/generate.js.map +1 -0
- package/dist/export-sync/index.d.ts +37 -0
- package/dist/export-sync/index.js +39 -0
- package/dist/export-sync/index.js.map +1 -0
- package/dist/export-sync/init.d.ts +123 -0
- package/dist/export-sync/init.js +234 -0
- package/dist/export-sync/init.js.map +1 -0
- package/dist/export-sync/manifest-host.d.ts +48 -0
- package/dist/export-sync/manifest-host.js +73 -0
- package/dist/export-sync/manifest-host.js.map +1 -0
- package/dist/export-sync/node.d.ts +20 -0
- package/dist/export-sync/node.js +101 -0
- package/dist/export-sync/node.js.map +1 -0
- package/dist/export-sync/push.d.ts +76 -0
- package/dist/export-sync/push.js +197 -0
- package/dist/export-sync/push.js.map +1 -0
- package/dist/export-sync/registry-sync-client.d.ts +59 -0
- package/dist/export-sync/registry-sync-client.js +97 -0
- package/dist/export-sync/registry-sync-client.js.map +1 -0
- package/dist/export-sync/sync.d.ts +47 -0
- package/dist/export-sync/sync.js +47 -0
- package/dist/export-sync/sync.js.map +1 -0
- package/dist/export-sync/typegen.d.ts +40 -0
- package/dist/export-sync/typegen.js +45 -0
- package/dist/export-sync/typegen.js.map +1 -0
- package/dist/export-sync/validate.d.ts +19 -0
- package/dist/export-sync/validate.js +102 -0
- package/dist/export-sync/validate.js.map +1 -0
- package/dist/export-sync/watch.d.ts +66 -0
- package/dist/export-sync/watch.js +70 -0
- package/dist/export-sync/watch.js.map +1 -0
- package/dist/operations/assets.d.ts +198 -0
- package/dist/operations/assets.js +75 -0
- package/dist/operations/assets.js.map +1 -0
- package/dist/operations/authorization.d.ts +49 -0
- package/dist/operations/authorization.js +128 -0
- package/dist/operations/authorization.js.map +1 -0
- package/dist/operations/changesets.d.ts +160 -0
- package/dist/operations/changesets.js +442 -0
- package/dist/operations/changesets.js.map +1 -0
- package/dist/operations/client.d.ts +49 -0
- package/dist/operations/client.js +57 -0
- package/dist/operations/client.js.map +1 -0
- package/dist/operations/core.d.ts +238 -0
- package/dist/operations/core.js +269 -0
- package/dist/operations/core.js.map +1 -0
- package/dist/operations/documents.d.ts +432 -0
- package/dist/operations/documents.js +344 -0
- package/dist/operations/documents.js.map +1 -0
- package/dist/operations/graph.d.ts +138 -0
- package/dist/operations/graph.js +78 -0
- package/dist/operations/graph.js.map +1 -0
- package/dist/operations/index.d.ts +19 -0
- package/dist/operations/index.js +20 -0
- package/dist/operations/index.js.map +1 -0
- package/dist/operations/members.d.ts +87 -0
- package/dist/operations/members.js +56 -0
- package/dist/operations/members.js.map +1 -0
- package/dist/operations/publishing.d.ts +89 -0
- package/dist/operations/publishing.js +57 -0
- package/dist/operations/publishing.js.map +1 -0
- package/dist/operations/references.d.ts +222 -0
- package/dist/operations/references.js +177 -0
- package/dist/operations/references.js.map +1 -0
- package/dist/operations/schema.d.ts +413 -0
- package/dist/operations/schema.js +138 -0
- package/dist/operations/schema.js.map +1 -0
- package/dist/operations/tokens.d.ts +79 -0
- package/dist/operations/tokens.js +102 -0
- package/dist/operations/tokens.js.map +1 -0
- package/dist/persistence/adapter.d.ts +79 -0
- package/dist/persistence/adapter.js +55 -0
- package/dist/persistence/adapter.js.map +1 -0
- package/dist/persistence/assets.d.ts +120 -0
- package/dist/persistence/assets.js +141 -0
- package/dist/persistence/assets.js.map +1 -0
- package/dist/persistence/backend-validation.d.ts +43 -0
- package/dist/persistence/backend-validation.js +57 -0
- package/dist/persistence/backend-validation.js.map +1 -0
- package/dist/persistence/cli-tokens.d.ts +50 -0
- package/dist/persistence/cli-tokens.js +66 -0
- package/dist/persistence/cli-tokens.js.map +1 -0
- package/dist/persistence/cms.d.ts +148 -0
- package/dist/persistence/cms.js +232 -0
- package/dist/persistence/cms.js.map +1 -0
- package/dist/persistence/contract-scenarios.d.ts +73 -0
- package/dist/persistence/contract-scenarios.js +496 -0
- package/dist/persistence/contract-scenarios.js.map +1 -0
- package/dist/persistence/core.d.ts +96 -0
- package/dist/persistence/core.js +110 -0
- package/dist/persistence/core.js.map +1 -0
- package/dist/persistence/graph.d.ts +61 -0
- package/dist/persistence/graph.js +98 -0
- package/dist/persistence/graph.js.map +1 -0
- package/dist/persistence/index.d.ts +22 -0
- package/dist/persistence/index.js +23 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/members.d.ts +70 -0
- package/dist/persistence/members.js +0 -0
- package/dist/persistence/members.js.map +1 -0
- package/dist/persistence/publishing.d.ts +59 -0
- package/dist/persistence/publishing.js +95 -0
- package/dist/persistence/publishing.js.map +1 -0
- package/dist/persistence/reference-extraction.d.ts +44 -0
- package/dist/persistence/reference-extraction.js +204 -0
- package/dist/persistence/reference-extraction.js.map +1 -0
- package/dist/persistence/reference-indexing.d.ts +68 -0
- package/dist/persistence/reference-indexing.js +112 -0
- package/dist/persistence/reference-indexing.js.map +1 -0
- package/dist/persistence/references.d.ts +257 -0
- package/dist/persistence/references.js +0 -0
- package/dist/persistence/references.js.map +1 -0
- package/dist/persistence/seed.d.ts +55 -0
- package/dist/persistence/seed.js +102 -0
- package/dist/persistence/seed.js.map +1 -0
- package/dist/persistence/self-maintaining-adapter.d.ts +41 -0
- package/dist/persistence/self-maintaining-adapter.js +79 -0
- package/dist/persistence/self-maintaining-adapter.js.map +1 -0
- package/dist/plugins/asset-storage.d.ts +76 -0
- package/dist/plugins/asset-storage.js +104 -0
- package/dist/plugins/asset-storage.js.map +1 -0
- package/dist/plugins/component-package.d.ts +54 -0
- package/dist/plugins/component-package.js +92 -0
- package/dist/plugins/component-package.js.map +1 -0
- package/dist/plugins/data-sources.d.ts +78 -0
- package/dist/plugins/data-sources.js +99 -0
- package/dist/plugins/data-sources.js.map +1 -0
- package/dist/plugins/examples.d.ts +47 -0
- package/dist/plugins/examples.js +205 -0
- package/dist/plugins/examples.js.map +1 -0
- package/dist/plugins/export-targets.d.ts +47 -0
- package/dist/plugins/export-targets.js +78 -0
- package/dist/plugins/export-targets.js.map +1 -0
- package/dist/plugins/field-types.d.ts +86 -0
- package/dist/plugins/field-types.js +93 -0
- package/dist/plugins/field-types.js.map +1 -0
- package/dist/plugins/hooks.d.ts +60 -0
- package/dist/plugins/hooks.js +94 -0
- package/dist/plugins/hooks.js.map +1 -0
- package/dist/plugins/index.d.ts +27 -0
- package/dist/plugins/index.js +28 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/marketplace.d.ts +90 -0
- package/dist/plugins/marketplace.js +110 -0
- package/dist/plugins/marketplace.js.map +1 -0
- package/dist/plugins/plugin.d.ts +107 -0
- package/dist/plugins/plugin.js +122 -0
- package/dist/plugins/plugin.js.map +1 -0
- package/dist/plugins/templates.d.ts +66 -0
- package/dist/plugins/templates.js +74 -0
- package/dist/plugins/templates.js.map +1 -0
- package/dist/project-graph/binding.d.ts +68 -0
- package/dist/project-graph/binding.js +39 -0
- package/dist/project-graph/binding.js.map +1 -0
- package/dist/project-graph/container.d.ts +126 -0
- package/dist/project-graph/container.js +221 -0
- package/dist/project-graph/container.js.map +1 -0
- package/dist/project-graph/edit.d.ts +66 -0
- package/dist/project-graph/edit.js +201 -0
- package/dist/project-graph/edit.js.map +1 -0
- package/dist/project-graph/fixtures.d.ts +51 -0
- package/dist/project-graph/fixtures.js +224 -0
- package/dist/project-graph/fixtures.js.map +1 -0
- package/dist/project-graph/ids.d.ts +17 -0
- package/dist/project-graph/ids.js +19 -0
- package/dist/project-graph/ids.js.map +1 -0
- package/dist/project-graph/index.d.ts +12 -0
- package/dist/project-graph/index.js +13 -0
- package/dist/project-graph/index.js.map +1 -0
- package/dist/project-graph/issues.d.ts +61 -0
- package/dist/project-graph/issues.js +44 -0
- package/dist/project-graph/issues.js.map +1 -0
- package/dist/project-graph/json.d.ts +6 -0
- package/dist/project-graph/json.js +10 -0
- package/dist/project-graph/json.js.map +1 -0
- package/dist/project-graph/node.d.ts +48 -0
- package/dist/project-graph/node.js +42 -0
- package/dist/project-graph/node.js.map +1 -0
- package/dist/project-graph/normalize.d.ts +17 -0
- package/dist/project-graph/normalize.js +110 -0
- package/dist/project-graph/normalize.js.map +1 -0
- package/dist/project-graph/serialize.d.ts +36 -0
- package/dist/project-graph/serialize.js +51 -0
- package/dist/project-graph/serialize.js.map +1 -0
- package/dist/project-graph/structure.d.ts +40 -0
- package/dist/project-graph/structure.js +36 -0
- package/dist/project-graph/structure.js.map +1 -0
- package/dist/project-graph/validate.d.ts +84 -0
- package/dist/project-graph/validate.js +158 -0
- package/dist/project-graph/validate.js.map +1 -0
- package/dist/rich-text/adapter.d.ts +43 -0
- package/dist/rich-text/adapter.js +24 -0
- package/dist/rich-text/adapter.js.map +1 -0
- package/dist/rich-text/dom-editor.d.ts +101 -0
- package/dist/rich-text/dom-editor.js +482 -0
- package/dist/rich-text/dom-editor.js.map +1 -0
- package/dist/rich-text/embed.d.ts +70 -0
- package/dist/rich-text/embed.js +70 -0
- package/dist/rich-text/embed.js.map +1 -0
- package/dist/rich-text/fixtures.d.ts +25 -0
- package/dist/rich-text/fixtures.js +115 -0
- package/dist/rich-text/fixtures.js.map +1 -0
- package/dist/rich-text/html.d.ts +5 -0
- package/dist/rich-text/html.js +309 -0
- package/dist/rich-text/html.js.map +1 -0
- package/dist/rich-text/index.d.ts +19 -0
- package/dist/rich-text/index.js +20 -0
- package/dist/rich-text/index.js.map +1 -0
- package/dist/rich-text/markdown.d.ts +7 -0
- package/dist/rich-text/markdown.js +235 -0
- package/dist/rich-text/markdown.js.map +1 -0
- package/dist/rich-text/portable-text.d.ts +50 -0
- package/dist/rich-text/portable-text.js +223 -0
- package/dist/rich-text/portable-text.js.map +1 -0
- package/dist/rich-text/registry.d.ts +48 -0
- package/dist/rich-text/registry.js +16 -0
- package/dist/rich-text/registry.js.map +1 -0
- package/dist/rich-text/render.d.ts +69 -0
- package/dist/rich-text/render.js +205 -0
- package/dist/rich-text/render.js.map +1 -0
- package/dist/rich-text/schema.d.ts +86 -0
- package/dist/rich-text/schema.js +80 -0
- package/dist/rich-text/schema.js.map +1 -0
- package/dist/rich-text/test-support.d.ts +7 -0
- package/dist/rich-text/test-support.js +8 -0
- package/dist/rich-text/test-support.js.map +1 -0
- package/dist/runtime-renderer/client.d.ts +14 -0
- package/dist/runtime-renderer/client.js +15 -0
- package/dist/runtime-renderer/client.js.map +1 -0
- package/dist/runtime-renderer/condition.d.ts +8 -0
- package/dist/runtime-renderer/condition.js +48 -0
- package/dist/runtime-renderer/condition.js.map +1 -0
- package/dist/runtime-renderer/content-client.d.ts +62 -0
- package/dist/runtime-renderer/content-client.js +37 -0
- package/dist/runtime-renderer/content-client.js.map +1 -0
- package/dist/runtime-renderer/context.d.ts +185 -0
- package/dist/runtime-renderer/context.js +7 -0
- package/dist/runtime-renderer/context.js.map +1 -0
- package/dist/runtime-renderer/fallback.d.ts +39 -0
- package/dist/runtime-renderer/fallback.js +45 -0
- package/dist/runtime-renderer/fallback.js.map +1 -0
- package/dist/runtime-renderer/index.d.ts +13 -0
- package/dist/runtime-renderer/index.js +13 -0
- package/dist/runtime-renderer/index.js.map +1 -0
- package/dist/runtime-renderer/primitives.d.ts +85 -0
- package/dist/runtime-renderer/primitives.js +442 -0
- package/dist/runtime-renderer/primitives.js.map +1 -0
- package/dist/runtime-renderer/render.d.ts +138 -0
- package/dist/runtime-renderer/render.js +825 -0
- package/dist/runtime-renderer/render.js.map +1 -0
- package/dist/runtime-renderer/rich-text.d.ts +26 -0
- package/dist/runtime-renderer/rich-text.js +113 -0
- package/dist/runtime-renderer/rich-text.js.map +1 -0
- package/dist/starter-kits/index.d.ts +13 -0
- package/dist/starter-kits/index.js +14 -0
- package/dist/starter-kits/index.js.map +1 -0
- package/dist/starter-kits/instantiate.d.ts +29 -0
- package/dist/starter-kits/instantiate.js +24 -0
- package/dist/starter-kits/instantiate.js.map +1 -0
- package/dist/starter-kits/kit.d.ts +61 -0
- package/dist/starter-kits/kit.js +37 -0
- package/dist/starter-kits/kit.js.map +1 -0
- package/dist/starter-kits/kits.d.ts +7 -0
- package/dist/starter-kits/kits.js +201 -0
- package/dist/starter-kits/kits.js.map +1 -0
- package/dist/starter-kits/manifests.d.ts +7 -0
- package/dist/starter-kits/manifests.js +111 -0
- package/dist/starter-kits/manifests.js.map +1 -0
- package/dist/starter-kits/upgrade.d.ts +39 -0
- package/dist/starter-kits/upgrade.js +54 -0
- package/dist/starter-kits/upgrade.js.map +1 -0
- package/dist/starter-kits/validate.d.ts +16 -0
- package/dist/starter-kits/validate.js +77 -0
- package/dist/starter-kits/validate.js.map +1 -0
- package/dist/studio-core/context.d.ts +27 -0
- package/dist/studio-core/context.js +44 -0
- package/dist/studio-core/context.js.map +1 -0
- package/dist/studio-core/errors.d.ts +58 -0
- package/dist/studio-core/errors.js +74 -0
- package/dist/studio-core/errors.js.map +1 -0
- package/dist/studio-core/fixtures.d.ts +18 -0
- package/dist/studio-core/fixtures.js +83 -0
- package/dist/studio-core/fixtures.js.map +1 -0
- package/dist/studio-core/ids.d.ts +22 -0
- package/dist/studio-core/ids.js +33 -0
- package/dist/studio-core/ids.js.map +1 -0
- package/dist/studio-core/index.d.ts +11 -0
- package/dist/studio-core/index.js +12 -0
- package/dist/studio-core/index.js.map +1 -0
- package/dist/studio-core/issues.d.ts +50 -0
- package/dist/studio-core/issues.js +33 -0
- package/dist/studio-core/issues.js.map +1 -0
- package/dist/studio-core/metadata.d.ts +109 -0
- package/dist/studio-core/metadata.js +74 -0
- package/dist/studio-core/metadata.js.map +1 -0
- package/dist/studio-core/repository.d.ts +73 -0
- package/dist/studio-core/repository.js +202 -0
- package/dist/studio-core/repository.js.map +1 -0
- package/dist/studio-core/sections.d.ts +73 -0
- package/dist/studio-core/sections.js +68 -0
- package/dist/studio-core/sections.js.map +1 -0
- package/dist/studio-core/validate.d.ts +15 -0
- package/dist/studio-core/validate.js +119 -0
- package/dist/studio-core/validate.js.map +1 -0
- package/dist/studio-core/workspace-config.d.ts +587 -0
- package/dist/studio-core/workspace-config.js +434 -0
- package/dist/studio-core/workspace-config.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import { documentKey } from './documents';
|
|
2
|
+
import { readAssetId, readDocumentRef } from './envelopes';
|
|
3
|
+
import { isFieldLocalized, resolveField } from './localization';
|
|
4
|
+
import { hasParentCycle, parentRelationField } from './hierarchy';
|
|
5
|
+
import { cmsIssue } from './issues';
|
|
6
|
+
/** Build a `DocumentStore` from a flat list of documents. */
|
|
7
|
+
export function createDocumentStore(docs) {
|
|
8
|
+
const byKey = new Map();
|
|
9
|
+
const issues = [];
|
|
10
|
+
for (const doc of docs) {
|
|
11
|
+
const key = documentKey(doc);
|
|
12
|
+
if (byKey.has(key)) {
|
|
13
|
+
issues.push(cmsIssue({
|
|
14
|
+
code: 'duplicate-document',
|
|
15
|
+
message: `Duplicate document "${doc.id}" in collection "${doc.collection}".`,
|
|
16
|
+
path: ['documents', key],
|
|
17
|
+
collectionId: doc.collection,
|
|
18
|
+
documentId: doc.id,
|
|
19
|
+
}));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
byKey.set(key, doc);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
issues,
|
|
26
|
+
has: (ref) => byKey.has(documentKey(ref)),
|
|
27
|
+
get: (ref) => byKey.get(documentKey(ref)),
|
|
28
|
+
list: () => [...byKey.values()],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isEmpty(value) {
|
|
32
|
+
if (value === undefined || value === null)
|
|
33
|
+
return true;
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return value.length === 0;
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return value.length === 0;
|
|
38
|
+
// EC-253: a plain object with no keys is empty — so a required `object` field
|
|
39
|
+
// whose value is `{}` is flagged missing. Object-shaped values that DO carry
|
|
40
|
+
// keys (relation refs `{collection,id}`, rich-text envelopes) are never empty.
|
|
41
|
+
if (isPlainObject(value))
|
|
42
|
+
return Object.keys(value).length === 0;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/** A non-null, non-array object — the shape an `object` field value takes (EC-253). */
|
|
46
|
+
function isPlainObject(value) {
|
|
47
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether a value must still be validated even though {@link isEmpty} is true
|
|
51
|
+
* (EC-253). A PRESENT plain object of an `object` field is validated even with no
|
|
52
|
+
* keys, so its required subfields surface (`{}` is not the same as absent); every
|
|
53
|
+
* other empty/absent value has nothing to validate — `required` owns presence.
|
|
54
|
+
*/
|
|
55
|
+
function isPresentObjectValue(field, value) {
|
|
56
|
+
return field.type === 'object' && isPlainObject(value);
|
|
57
|
+
}
|
|
58
|
+
// EC-277: relation values are self-typed (`{ type:'reference', collection, id }`);
|
|
59
|
+
// `readDocumentRef` unwraps that AND legacy bare `{ collection, id }` (lazy-on-read).
|
|
60
|
+
const coerceRef = readDocumentRef;
|
|
61
|
+
function validateScalarType(ctx, issues) {
|
|
62
|
+
const { field, value, path, collection, documentId } = ctx;
|
|
63
|
+
const bad = (detail) => issues.push(cmsIssue({
|
|
64
|
+
code: 'invalid-field-value',
|
|
65
|
+
message: `Field "${field.name}" ${detail}.`,
|
|
66
|
+
path,
|
|
67
|
+
collectionId: collection.id,
|
|
68
|
+
documentId,
|
|
69
|
+
meta: { field: field.name, type: field.type },
|
|
70
|
+
}));
|
|
71
|
+
switch (field.type) {
|
|
72
|
+
case 'text':
|
|
73
|
+
if (typeof value !== 'string')
|
|
74
|
+
bad('expects a string');
|
|
75
|
+
break;
|
|
76
|
+
case 'richText':
|
|
77
|
+
// A rich-text value is either a plain string (CMS markdown/HTML) or the
|
|
78
|
+
// structured @elytracms/rich-text storage envelope (an object) carrying
|
|
79
|
+
// formatting + composition embeds (EC-159/EC-189). Mirrors the prop-side
|
|
80
|
+
// `checkScalarType`; deep validation is delegated (the renderer parse guard
|
|
81
|
+
// + the injected rich-text-embed validator).
|
|
82
|
+
if (typeof value !== 'string' && !(typeof value === 'object' && value !== null)) {
|
|
83
|
+
bad('expects rich-text content (a string or a rich-text value)');
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case 'number':
|
|
87
|
+
if (typeof value !== 'number' || Number.isNaN(value))
|
|
88
|
+
bad('expects a number');
|
|
89
|
+
break;
|
|
90
|
+
case 'boolean':
|
|
91
|
+
if (typeof value !== 'boolean')
|
|
92
|
+
bad('expects a boolean');
|
|
93
|
+
break;
|
|
94
|
+
case 'date':
|
|
95
|
+
// Accept ISO date strings or epoch numbers; reject anything unparseable.
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
if (Number.isNaN(Date.parse(value)))
|
|
98
|
+
bad('expects a valid date string');
|
|
99
|
+
}
|
|
100
|
+
else if (typeof value !== 'number') {
|
|
101
|
+
bad('expects a date (ISO string or epoch number)');
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case 'select': {
|
|
105
|
+
const allowed = new Set(field.options.map((o) => o.value));
|
|
106
|
+
const values = field.multiple ? (Array.isArray(value) ? value : [value]) : [value];
|
|
107
|
+
for (const v of values) {
|
|
108
|
+
if (typeof v !== 'string' || !allowed.has(v)) {
|
|
109
|
+
bad(`has value "${String(v)}" not among its options`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'object':
|
|
115
|
+
// EC-253: only the TOP-LEVEL shape is checked here (an array of objects for
|
|
116
|
+
// `many`, a single object for `one`); recursion into the subfields lives in
|
|
117
|
+
// `validateObject`, which skips malformed items this already reported.
|
|
118
|
+
if (field.cardinality === 'many') {
|
|
119
|
+
if (!Array.isArray(value) || value.some((item) => !isPlainObject(item))) {
|
|
120
|
+
bad('expects a list of objects');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (!isPlainObject(value)) {
|
|
124
|
+
bad('expects an object');
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case 'relation':
|
|
128
|
+
case 'asset':
|
|
129
|
+
case 'blocks':
|
|
130
|
+
// Validated structurally elsewhere (targets / composition), not as scalars.
|
|
131
|
+
break;
|
|
132
|
+
default: {
|
|
133
|
+
// Exhaustiveness: a new field type must add a case above (EC-253).
|
|
134
|
+
const _exhaustive = field;
|
|
135
|
+
void _exhaustive;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Validate a `blocks` composition field value via the injected callback (EC-188).
|
|
141
|
+
* The composition tree is validated against the field's `allow` vocabulary + the
|
|
142
|
+
* component registry by the studio-supplied `validateComposition`; each finding is
|
|
143
|
+
* wrapped into a field-keyed issue so it surfaces inline like any other field
|
|
144
|
+
* error. A no-op when the field is not a `blocks` field or no validator is wired
|
|
145
|
+
* (e.g. headless contexts without a registry).
|
|
146
|
+
*/
|
|
147
|
+
function validateBlocks(ctx, issues, validateComposition) {
|
|
148
|
+
if (ctx.field.type !== 'blocks' || !validateComposition)
|
|
149
|
+
return;
|
|
150
|
+
const { field, value, path, collection, documentId } = ctx;
|
|
151
|
+
for (const finding of validateComposition(value, field)) {
|
|
152
|
+
issues.push(cmsIssue({
|
|
153
|
+
code: 'invalid-field-value',
|
|
154
|
+
severity: finding.severity ?? 'error',
|
|
155
|
+
message: `Field "${field.name}": ${finding.message}`,
|
|
156
|
+
path,
|
|
157
|
+
collectionId: collection.id,
|
|
158
|
+
documentId,
|
|
159
|
+
meta: {
|
|
160
|
+
field: field.name,
|
|
161
|
+
type: 'blocks',
|
|
162
|
+
...(finding.nodePath ? { nodePath: finding.nodePath } : {}),
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Validate the composition embeds inside a `richText` field value via the injected
|
|
169
|
+
* callback (EC-189). A rich-text value may carry `componentEmbed` nodes, each
|
|
170
|
+
* holding a single `ComponentNode` constrained by the field's `allow` vocabulary —
|
|
171
|
+
* the SAME leash a `blocks` field uses, applied to prose embeds. The studio-supplied
|
|
172
|
+
* `validateRichTextEmbeds` walks the embeds and validates each via
|
|
173
|
+
* `validateCompositionValue`; each finding is wrapped into a field-keyed issue,
|
|
174
|
+
* carrying the rich-text node path. A no-op when the field is not `richText` or no
|
|
175
|
+
* validator is wired (e.g. headless contexts without a registry).
|
|
176
|
+
*/
|
|
177
|
+
function validateRichTextEmbeds(ctx, issues, validateRichText) {
|
|
178
|
+
if (ctx.field.type !== 'richText' || !validateRichText)
|
|
179
|
+
return;
|
|
180
|
+
const { field, value, path, collection, documentId } = ctx;
|
|
181
|
+
for (const finding of validateRichText(value, field)) {
|
|
182
|
+
issues.push(cmsIssue({
|
|
183
|
+
code: 'invalid-field-value',
|
|
184
|
+
severity: finding.severity ?? 'error',
|
|
185
|
+
message: `Field "${field.name}": ${finding.message}`,
|
|
186
|
+
path,
|
|
187
|
+
collectionId: collection.id,
|
|
188
|
+
documentId,
|
|
189
|
+
meta: {
|
|
190
|
+
field: field.name,
|
|
191
|
+
type: 'richText',
|
|
192
|
+
...(finding.nodePath ? { nodePath: finding.nodePath } : {}),
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function validateConstraints(ctx, issues) {
|
|
198
|
+
const { field, value, path, collection, documentId } = ctx;
|
|
199
|
+
const rules = field.validation;
|
|
200
|
+
if (!rules)
|
|
201
|
+
return;
|
|
202
|
+
const push = (detail) => issues.push(cmsIssue({
|
|
203
|
+
code: 'invalid-field-value',
|
|
204
|
+
message: `Field "${field.name}" ${detail}.`,
|
|
205
|
+
path,
|
|
206
|
+
collectionId: collection.id,
|
|
207
|
+
documentId,
|
|
208
|
+
meta: { field: field.name },
|
|
209
|
+
}));
|
|
210
|
+
if (typeof value === 'string') {
|
|
211
|
+
if (rules.min !== undefined && value.length < rules.min)
|
|
212
|
+
push(`is shorter than min length ${rules.min}`);
|
|
213
|
+
if (rules.max !== undefined && value.length > rules.max)
|
|
214
|
+
push(`is longer than max length ${rules.max}`);
|
|
215
|
+
if (rules.pattern !== undefined) {
|
|
216
|
+
let re;
|
|
217
|
+
try {
|
|
218
|
+
re = new RegExp(rules.pattern);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
re = undefined;
|
|
222
|
+
}
|
|
223
|
+
if (re && !re.test(value))
|
|
224
|
+
push(`does not match pattern ${rules.pattern}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (typeof value === 'number') {
|
|
228
|
+
if (rules.min !== undefined && value < rules.min)
|
|
229
|
+
push(`is less than min ${rules.min}`);
|
|
230
|
+
if (rules.max !== undefined && value > rules.max)
|
|
231
|
+
push(`is greater than max ${rules.max}`);
|
|
232
|
+
}
|
|
233
|
+
else if (Array.isArray(value)) {
|
|
234
|
+
if (rules.min !== undefined && value.length < rules.min)
|
|
235
|
+
push(`has fewer than ${rules.min} items`);
|
|
236
|
+
if (rules.max !== undefined && value.length > rules.max)
|
|
237
|
+
push(`has more than ${rules.max} items`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function validateRelation(ctx, issues) {
|
|
241
|
+
const { field, value, path, collection, registry, store, documentId } = ctx;
|
|
242
|
+
if (field.type !== 'relation')
|
|
243
|
+
return;
|
|
244
|
+
if (!registry.has(field.target)) {
|
|
245
|
+
issues.push(cmsIssue({
|
|
246
|
+
code: 'unknown-relation-target',
|
|
247
|
+
message: `Relation field "${field.name}" targets unknown collection "${field.target}".`,
|
|
248
|
+
path,
|
|
249
|
+
collectionId: collection.id,
|
|
250
|
+
documentId,
|
|
251
|
+
meta: { field: field.name, target: field.target },
|
|
252
|
+
}));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const refs = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
|
|
256
|
+
if (field.cardinality === 'one' && Array.isArray(value) && value.length > 1) {
|
|
257
|
+
issues.push(cmsIssue({
|
|
258
|
+
code: 'cardinality-violation',
|
|
259
|
+
message: `Relation field "${field.name}" is single-valued but has ${value.length} targets.`,
|
|
260
|
+
path,
|
|
261
|
+
collectionId: collection.id,
|
|
262
|
+
documentId,
|
|
263
|
+
meta: { field: field.name, cardinality: 'one' },
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
refs.forEach((raw, i) => {
|
|
267
|
+
const ref = coerceRef(raw);
|
|
268
|
+
if (!ref) {
|
|
269
|
+
issues.push(cmsIssue({
|
|
270
|
+
code: 'invalid-field-value',
|
|
271
|
+
message: `Relation field "${field.name}" holds a value that is not a document reference.`,
|
|
272
|
+
path: [...path, i],
|
|
273
|
+
collectionId: collection.id,
|
|
274
|
+
documentId,
|
|
275
|
+
meta: { field: field.name },
|
|
276
|
+
}));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (ref.collection !== field.target) {
|
|
280
|
+
issues.push(cmsIssue({
|
|
281
|
+
code: 'unknown-relation-target',
|
|
282
|
+
message: `Relation field "${field.name}" points at collection "${ref.collection}" but targets "${field.target}".`,
|
|
283
|
+
path: [...path, i],
|
|
284
|
+
collectionId: collection.id,
|
|
285
|
+
documentId,
|
|
286
|
+
meta: { field: field.name, target: field.target, got: ref.collection },
|
|
287
|
+
}));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (store) {
|
|
291
|
+
const target = store.get(ref);
|
|
292
|
+
if (!target) {
|
|
293
|
+
issues.push(cmsIssue({
|
|
294
|
+
code: 'unknown-relation-target',
|
|
295
|
+
message: `Relation field "${field.name}" references missing document "${ref.id}" in "${ref.collection}".`,
|
|
296
|
+
path: [...path, i],
|
|
297
|
+
collectionId: collection.id,
|
|
298
|
+
documentId,
|
|
299
|
+
meta: { field: field.name, target: ref.collection, id: ref.id },
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
else if (ctx.isSourcePublished && !ctx.isPublished(ref)) {
|
|
303
|
+
// EC-173 weak-reference aftermath: the source is published but the
|
|
304
|
+
// target is not, so published delivery serves the declared fallback
|
|
305
|
+
// for this reference. Explicit and visible — but a warning, never an
|
|
306
|
+
// error: weak references must not block saving or re-publishing the
|
|
307
|
+
// source. Re-publishing the target clears it.
|
|
308
|
+
issues.push(cmsIssue({
|
|
309
|
+
code: 'unpublished-relation-target',
|
|
310
|
+
severity: 'warning',
|
|
311
|
+
message: `Relation field "${field.name}" references unpublished document "${ref.id}" in "${ref.collection}" — the published site shows a fallback there.`,
|
|
312
|
+
path: [...path, i],
|
|
313
|
+
collectionId: collection.id,
|
|
314
|
+
documentId,
|
|
315
|
+
meta: { field: field.name, target: ref.collection, id: ref.id },
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function validateAsset(ctx, issues) {
|
|
322
|
+
const { field, value, path, collection, registry, store, documentId } = ctx;
|
|
323
|
+
if (field.type !== 'asset')
|
|
324
|
+
return;
|
|
325
|
+
const assetCollections = registry.assetCollections();
|
|
326
|
+
if (assetCollections.length === 0) {
|
|
327
|
+
issues.push(cmsIssue({
|
|
328
|
+
code: 'invalid-field-config',
|
|
329
|
+
message: `Asset field "${field.name}" requires an asset collection, but none is registered.`,
|
|
330
|
+
path,
|
|
331
|
+
collectionId: collection.id,
|
|
332
|
+
documentId,
|
|
333
|
+
meta: { field: field.name },
|
|
334
|
+
}));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const ids = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
|
|
338
|
+
if (field.cardinality === 'one' && Array.isArray(value) && value.length > 1) {
|
|
339
|
+
issues.push(cmsIssue({
|
|
340
|
+
code: 'cardinality-violation',
|
|
341
|
+
message: `Asset field "${field.name}" is single-valued but has ${value.length} targets.`,
|
|
342
|
+
path,
|
|
343
|
+
collectionId: collection.id,
|
|
344
|
+
documentId,
|
|
345
|
+
meta: { field: field.name, cardinality: 'one' },
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
ids.forEach((rawValue, i) => {
|
|
349
|
+
// An asset value is the id of a document in some asset collection — self-typed
|
|
350
|
+
// (`{ type:'asset', id }`) or legacy bare id (EC-277 lazy-on-read).
|
|
351
|
+
const raw = readAssetId(rawValue);
|
|
352
|
+
if (raw === undefined) {
|
|
353
|
+
issues.push(cmsIssue({
|
|
354
|
+
code: 'invalid-field-value',
|
|
355
|
+
message: `Asset field "${field.name}" expects an asset id.`,
|
|
356
|
+
path: [...path, i],
|
|
357
|
+
collectionId: collection.id,
|
|
358
|
+
documentId,
|
|
359
|
+
meta: { field: field.name },
|
|
360
|
+
}));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// Asset existence is checked against the asset library when the caller
|
|
364
|
+
// supplies an oracle (assets live in their own repository, not as CMS
|
|
365
|
+
// documents — EC-212 follow-up); otherwise it falls back to the document
|
|
366
|
+
// store (fixtures that model assets as documents). With neither, existence
|
|
367
|
+
// is simply not checked (`undefined` → skip), never wrongly flagged.
|
|
368
|
+
const exists = ctx.assetExists
|
|
369
|
+
? ctx.assetExists(raw)
|
|
370
|
+
: store
|
|
371
|
+
? assetCollections.some((c) => store.has({ collection: c.id, id: raw }))
|
|
372
|
+
: undefined;
|
|
373
|
+
if (exists === false) {
|
|
374
|
+
issues.push(cmsIssue({
|
|
375
|
+
code: 'unknown-asset',
|
|
376
|
+
message: `Asset field "${field.name}" references unknown asset "${raw}".`,
|
|
377
|
+
path: [...path, i],
|
|
378
|
+
collectionId: collection.id,
|
|
379
|
+
documentId,
|
|
380
|
+
meta: { field: field.name, asset: raw },
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Validate an `object` / repeater field value (EC-253) by recursing into each
|
|
387
|
+
* item's subfields. `cardinality: 'many'` is an array of objects (each item keyed
|
|
388
|
+
* by index in the path); `'one'` is a single object. Malformed items (already
|
|
389
|
+
* reported by {@link validateScalarType}) are skipped. The same {@link FieldCtx}
|
|
390
|
+
* (registry/store/asset oracle) is threaded so nested relation/asset subfields get
|
|
391
|
+
* the same target-existence checks they would at the top level.
|
|
392
|
+
*/
|
|
393
|
+
function validateObject(ctx, issues, v) {
|
|
394
|
+
const { field } = ctx;
|
|
395
|
+
if (field.type !== 'object')
|
|
396
|
+
return;
|
|
397
|
+
const items = field.cardinality === 'many' ? (Array.isArray(ctx.value) ? ctx.value : []) : [ctx.value];
|
|
398
|
+
items.forEach((item, index) => {
|
|
399
|
+
if (!isPlainObject(item))
|
|
400
|
+
return;
|
|
401
|
+
const itemPath = field.cardinality === 'many' ? [...ctx.path, index] : ctx.path;
|
|
402
|
+
for (const sub of field.fields) {
|
|
403
|
+
const subValue = item[sub.name];
|
|
404
|
+
const subPath = [...itemPath, sub.name];
|
|
405
|
+
if (isEmpty(subValue)) {
|
|
406
|
+
if (sub.validation?.required) {
|
|
407
|
+
issues.push(cmsIssue({
|
|
408
|
+
code: 'missing-required-field',
|
|
409
|
+
message: `Required field "${sub.name}" is missing.`,
|
|
410
|
+
path: subPath,
|
|
411
|
+
collectionId: ctx.collection.id,
|
|
412
|
+
documentId: ctx.documentId,
|
|
413
|
+
meta: { field: sub.name },
|
|
414
|
+
}));
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
// EC-253 (C1): an optional but PRESENT nested object still recurses so its
|
|
418
|
+
// own required subfields surface; any other empty/absent value is skipped.
|
|
419
|
+
if (!isPresentObjectValue(sub, subValue))
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
validateFieldValue({ ...ctx, field: sub, value: subValue, path: subPath }, issues, v);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Run every per-field validator for one field/value/path. The single dispatch
|
|
428
|
+
* point shared by the document loop and the {@link validateObject} recursion, so
|
|
429
|
+
* a subfield is validated exactly as a top-level field would be (EC-253).
|
|
430
|
+
*/
|
|
431
|
+
function validateFieldValue(ctx, issues, v) {
|
|
432
|
+
validateScalarType(ctx, issues);
|
|
433
|
+
validateConstraints(ctx, issues);
|
|
434
|
+
validateRelation(ctx, issues);
|
|
435
|
+
validateAsset(ctx, issues);
|
|
436
|
+
validateBlocks(ctx, issues, v.validateComposition);
|
|
437
|
+
validateRichTextEmbeds(ctx, issues, v.validateRichText);
|
|
438
|
+
validateObject(ctx, issues, v);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Validate a document against its collection (EC-016/017/018). Returns structured
|
|
442
|
+
* issues; never throws. Checks unknown collection, missing required fields,
|
|
443
|
+
* scalar type correctness, declared constraints, relation targets, asset
|
|
444
|
+
* references, and required localized values across configured locales.
|
|
445
|
+
*/
|
|
446
|
+
export function validateDocument(registry, doc, options = {}) {
|
|
447
|
+
const issues = [];
|
|
448
|
+
const collection = registry.get(doc.collection);
|
|
449
|
+
const basePath = ['documents', documentKey(doc)];
|
|
450
|
+
if (!collection) {
|
|
451
|
+
issues.push(cmsIssue({
|
|
452
|
+
code: 'unknown-collection',
|
|
453
|
+
message: `Document "${doc.id}" references unknown collection "${doc.collection}".`,
|
|
454
|
+
path: basePath,
|
|
455
|
+
documentId: doc.id,
|
|
456
|
+
meta: { collection: doc.collection },
|
|
457
|
+
}));
|
|
458
|
+
return issues;
|
|
459
|
+
}
|
|
460
|
+
const { store, localeConfig, validateComposition } = options;
|
|
461
|
+
const validateRichText = options.validateRichTextEmbeds;
|
|
462
|
+
// EC-224: published-ness is the version pointer, supplied by the caller. With
|
|
463
|
+
// no predicate, nothing is treated as published (the EC-173 warning is inert).
|
|
464
|
+
const isPublished = options.isPublished ?? (() => false);
|
|
465
|
+
const isSourcePublished = isPublished({ collection: doc.collection, id: doc.id });
|
|
466
|
+
for (const field of collection.fields) {
|
|
467
|
+
const fieldPath = [...basePath, 'values', field.name];
|
|
468
|
+
const localized = isFieldLocalized(collection, field.name);
|
|
469
|
+
// Determine the locales over which we validate this field's value.
|
|
470
|
+
const locales = localized && localeConfig ? localeConfig.locales : [localeConfig?.default ?? '*'];
|
|
471
|
+
const defaultLocale = localeConfig
|
|
472
|
+
? doc.defaultLocale ?? localeConfig.default
|
|
473
|
+
: undefined;
|
|
474
|
+
for (const locale of locales) {
|
|
475
|
+
// The fallback-resolved value (what would render) is used for type/constraint
|
|
476
|
+
// checks; the *raw* per-locale value drives the localized-required check so we
|
|
477
|
+
// can distinguish "genuinely missing" from "satisfied via default-locale fallback".
|
|
478
|
+
const resolved = localized
|
|
479
|
+
? localeConfig
|
|
480
|
+
? resolveField(collection, doc, field.name, locale, localeConfig).value
|
|
481
|
+
: doc.localized?.[locale]?.[field.name]
|
|
482
|
+
: doc.values[field.name];
|
|
483
|
+
const rawLocaleValue = localized ? doc.localized?.[locale]?.[field.name] : resolved;
|
|
484
|
+
const path = localized ? [...basePath, 'localized', locale, field.name] : fieldPath;
|
|
485
|
+
// Required check.
|
|
486
|
+
if (field.validation?.required) {
|
|
487
|
+
if (!localized) {
|
|
488
|
+
if (isEmpty(resolved)) {
|
|
489
|
+
issues.push(cmsIssue({
|
|
490
|
+
code: 'missing-required-field',
|
|
491
|
+
message: `Required field "${field.name}" is missing.`,
|
|
492
|
+
path,
|
|
493
|
+
collectionId: collection.id,
|
|
494
|
+
documentId: doc.id,
|
|
495
|
+
meta: { field: field.name },
|
|
496
|
+
}));
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
const isDefault = locale === defaultLocale;
|
|
502
|
+
if (isDefault && isEmpty(rawLocaleValue)) {
|
|
503
|
+
// A required localized field MUST have a value in the default locale —
|
|
504
|
+
// it is the fallback source for every other locale. Missing here is an
|
|
505
|
+
// error (no fallback can rescue it).
|
|
506
|
+
issues.push(cmsIssue({
|
|
507
|
+
code: 'missing-localized-value',
|
|
508
|
+
message: `Required localized field "${field.name}" is missing for the default locale "${locale}".`,
|
|
509
|
+
path,
|
|
510
|
+
collectionId: collection.id,
|
|
511
|
+
documentId: doc.id,
|
|
512
|
+
meta: { field: field.name, locale },
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
else if (!isDefault && isEmpty(rawLocaleValue) && isEmpty(resolved)) {
|
|
516
|
+
// Non-default locale missing AND no fallback value available.
|
|
517
|
+
issues.push(cmsIssue({
|
|
518
|
+
code: 'missing-localized-value',
|
|
519
|
+
severity: 'warning',
|
|
520
|
+
message: `Localized field "${field.name}" has no value for locale "${locale}" and no fallback.`,
|
|
521
|
+
path,
|
|
522
|
+
collectionId: collection.id,
|
|
523
|
+
documentId: doc.id,
|
|
524
|
+
meta: { field: field.name, locale },
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
|
+
if (isEmpty(resolved))
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (isEmpty(resolved) && !isPresentObjectValue(field, resolved))
|
|
532
|
+
continue;
|
|
533
|
+
const value = resolved;
|
|
534
|
+
const ctx = {
|
|
535
|
+
collection,
|
|
536
|
+
field,
|
|
537
|
+
value,
|
|
538
|
+
path,
|
|
539
|
+
registry,
|
|
540
|
+
store,
|
|
541
|
+
documentId: doc.id,
|
|
542
|
+
isSourcePublished,
|
|
543
|
+
isPublished,
|
|
544
|
+
assetExists: options.assetExists,
|
|
545
|
+
};
|
|
546
|
+
validateFieldValue(ctx, issues, { validateComposition, validateRichText });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// EC-218: a `parent` self-relation must not form a cycle. Needs the store to
|
|
550
|
+
// walk the chain (other documents); the walk is cycle-safe, so this only
|
|
551
|
+
// reports — it never loops. Only checked when the collection declares a parent
|
|
552
|
+
// self-relation and a store is supplied.
|
|
553
|
+
if (store && parentRelationField(collection)) {
|
|
554
|
+
if (hasParentCycle(doc, (id) => store.get({ collection: collection.id, id }))) {
|
|
555
|
+
issues.push(cmsIssue({
|
|
556
|
+
code: 'hierarchy-cycle',
|
|
557
|
+
message: `Document "${doc.id}" is its own ancestor — the parent chain forms a cycle.`,
|
|
558
|
+
path: [...basePath, 'values', 'parent'],
|
|
559
|
+
collectionId: collection.id,
|
|
560
|
+
documentId: doc.id,
|
|
561
|
+
meta: { field: 'parent' },
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return issues;
|
|
566
|
+
}
|
|
567
|
+
/** Validate every document in a list against the registry. */
|
|
568
|
+
export function validateDocuments(registry, docs, options = {}) {
|
|
569
|
+
return docs.flatMap((d) => validateDocument(registry, d, options));
|
|
570
|
+
}
|
|
571
|
+
// --- EC-190: props as fields -------------------------------------------------
|
|
572
|
+
// Component props are field-defs (the same vocabulary as document fields). The
|
|
573
|
+
// helpers below are the prop-side mirror of the document validators: pure,
|
|
574
|
+
// store-free type/constraint checks plus a prop-keyed `validateComponentProps`.
|
|
575
|
+
// They share field-type semantics with the document path; `fields.test.ts`
|
|
576
|
+
// guards against drift between the two.
|
|
577
|
+
/**
|
|
578
|
+
* Pure scalar/structural type check for a field value (EC-190). Returns a human
|
|
579
|
+
* detail string when the value is the wrong shape for the field type, or null
|
|
580
|
+
* when it is acceptable. Existence checks (relation/asset targets) are NOT done
|
|
581
|
+
* here — they need a document store. Assumes a present value (the `required`
|
|
582
|
+
* check owns presence).
|
|
583
|
+
*/
|
|
584
|
+
export function checkScalarType(field, value) {
|
|
585
|
+
switch (field.type) {
|
|
586
|
+
case 'text':
|
|
587
|
+
return typeof value === 'string' ? null : 'expects a string';
|
|
588
|
+
case 'richText':
|
|
589
|
+
// A rich-text value is either a plain string (CMS markdown/HTML) or the
|
|
590
|
+
// structured @elytracms/rich-text storage envelope (an object) used by the
|
|
591
|
+
// canvas RichText component. Deep validation is delegated (the renderer's
|
|
592
|
+
// parse guard / a future injected rich-text validator, mirroring blocks).
|
|
593
|
+
return typeof value === 'string' || (typeof value === 'object' && value !== null)
|
|
594
|
+
? null
|
|
595
|
+
: 'expects rich-text content (a string or a rich-text value)';
|
|
596
|
+
case 'number':
|
|
597
|
+
return typeof value === 'number' && !Number.isNaN(value) ? null : 'expects a number';
|
|
598
|
+
case 'boolean':
|
|
599
|
+
return typeof value === 'boolean' ? null : 'expects a boolean';
|
|
600
|
+
case 'date':
|
|
601
|
+
if (typeof value === 'string') {
|
|
602
|
+
return Number.isNaN(Date.parse(value)) ? 'expects a valid date string' : null;
|
|
603
|
+
}
|
|
604
|
+
return typeof value === 'number' ? null : 'expects a date (ISO string or epoch number)';
|
|
605
|
+
case 'select': {
|
|
606
|
+
const allowed = new Set(field.options.map((o) => o.value));
|
|
607
|
+
const values = field.multiple ? (Array.isArray(value) ? value : [value]) : [value];
|
|
608
|
+
for (const v of values) {
|
|
609
|
+
if (typeof v !== 'string' || !allowed.has(v)) {
|
|
610
|
+
return `has value "${String(v)}" not among its options`;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
case 'object':
|
|
616
|
+
// EC-253: top-level shape only (deep subfield validation lives in
|
|
617
|
+
// `validateComponentProps`, which needs the same per-field machinery).
|
|
618
|
+
if (field.cardinality === 'many') {
|
|
619
|
+
return Array.isArray(value) && value.every((item) => isPlainObject(item))
|
|
620
|
+
? null
|
|
621
|
+
: 'expects a list of objects';
|
|
622
|
+
}
|
|
623
|
+
return isPlainObject(value) ? null : 'expects an object';
|
|
624
|
+
case 'relation':
|
|
625
|
+
case 'asset':
|
|
626
|
+
case 'blocks':
|
|
627
|
+
// relation / asset / blocks are validated structurally, not as scalars.
|
|
628
|
+
return null;
|
|
629
|
+
default: {
|
|
630
|
+
// Exhaustiveness: a new field type must add a case above (EC-253).
|
|
631
|
+
const _exhaustive = field;
|
|
632
|
+
void _exhaustive;
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Pure declarative-constraint check (min/max/pattern) for a field value (EC-190).
|
|
639
|
+
* Returns the list of human detail strings for each violation (empty = valid).
|
|
640
|
+
* Mirrors `validateConstraints` without the issue-construction coupling.
|
|
641
|
+
*/
|
|
642
|
+
export function checkConstraints(field, value) {
|
|
643
|
+
const out = [];
|
|
644
|
+
const rules = field.validation;
|
|
645
|
+
if (!rules)
|
|
646
|
+
return out;
|
|
647
|
+
if (typeof value === 'string') {
|
|
648
|
+
if (rules.min !== undefined && value.length < rules.min)
|
|
649
|
+
out.push(`is shorter than min length ${rules.min}`);
|
|
650
|
+
if (rules.max !== undefined && value.length > rules.max)
|
|
651
|
+
out.push(`is longer than max length ${rules.max}`);
|
|
652
|
+
if (rules.pattern !== undefined) {
|
|
653
|
+
let re;
|
|
654
|
+
try {
|
|
655
|
+
re = new RegExp(rules.pattern);
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
re = undefined;
|
|
659
|
+
}
|
|
660
|
+
if (re && !re.test(value))
|
|
661
|
+
out.push(`does not match pattern ${rules.pattern}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
else if (typeof value === 'number') {
|
|
665
|
+
if (rules.min !== undefined && value < rules.min)
|
|
666
|
+
out.push(`is less than min ${rules.min}`);
|
|
667
|
+
if (rules.max !== undefined && value > rules.max)
|
|
668
|
+
out.push(`is greater than max ${rules.max}`);
|
|
669
|
+
}
|
|
670
|
+
else if (Array.isArray(value)) {
|
|
671
|
+
if (rules.min !== undefined && value.length < rules.min)
|
|
672
|
+
out.push(`has fewer than ${rules.min} items`);
|
|
673
|
+
if (rules.max !== undefined && value.length > rules.max)
|
|
674
|
+
out.push(`has more than ${rules.max} items`);
|
|
675
|
+
}
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
678
|
+
function checkRelationStructure(field, value) {
|
|
679
|
+
const out = [];
|
|
680
|
+
if (field.cardinality === 'one' && Array.isArray(value) && value.length > 1) {
|
|
681
|
+
out.push(`is single-valued but has ${value.length} targets`);
|
|
682
|
+
}
|
|
683
|
+
const refs = Array.isArray(value) ? value : [value];
|
|
684
|
+
for (const raw of refs) {
|
|
685
|
+
const ref = coerceRef(raw);
|
|
686
|
+
if (!ref)
|
|
687
|
+
out.push('holds a value that is not a document reference');
|
|
688
|
+
else if (ref.collection !== field.target) {
|
|
689
|
+
out.push(`points at collection "${ref.collection}" but targets "${field.target}"`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return out;
|
|
693
|
+
}
|
|
694
|
+
function checkAssetStructure(field, value) {
|
|
695
|
+
const out = [];
|
|
696
|
+
if (field.cardinality === 'one' && Array.isArray(value) && value.length > 1) {
|
|
697
|
+
out.push(`is single-valued but has ${value.length} targets`);
|
|
698
|
+
}
|
|
699
|
+
const ids = Array.isArray(value) ? value : [value];
|
|
700
|
+
for (const raw of ids) {
|
|
701
|
+
if (typeof raw !== 'string')
|
|
702
|
+
out.push('expects an asset id string');
|
|
703
|
+
}
|
|
704
|
+
return out;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Build a pure, store-free validator for a single field-def's value (EC-190).
|
|
708
|
+
* Used by the renderer (invalid static prop → EC-015 visible fallback) and the
|
|
709
|
+
* inspector. Returns true when the value is structurally acceptable for the field
|
|
710
|
+
* type; absent values (undefined/null/'') are acceptable — the `required` check
|
|
711
|
+
* owns presence. Relation/asset existence and composition depth are validated
|
|
712
|
+
* elsewhere (they need a store / registry).
|
|
713
|
+
*/
|
|
714
|
+
export function fieldValueValidator(field) {
|
|
715
|
+
return (value) => {
|
|
716
|
+
if (isEmpty(value))
|
|
717
|
+
return true;
|
|
718
|
+
return checkScalarType(field, value) === null && checkConstraints(field, value).length === 0;
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Lint a field-def used as a component prop (EC-190): document-only attributes
|
|
723
|
+
* (`filterable`, `validation.unique`) make no sense on a prop and surface as
|
|
724
|
+
* structured issues, never silently ignored. Returns [] for a valid prop
|
|
725
|
+
* field-def.
|
|
726
|
+
*/
|
|
727
|
+
export function assertPropFieldDef(field) {
|
|
728
|
+
const issues = [];
|
|
729
|
+
if (field.filterable) {
|
|
730
|
+
issues.push(cmsIssue({
|
|
731
|
+
code: 'invalid-field-config',
|
|
732
|
+
message: `Component prop "${field.name}" declares "filterable", which only applies to document fields.`,
|
|
733
|
+
path: ['props', field.name],
|
|
734
|
+
meta: { field: field.name },
|
|
735
|
+
}));
|
|
736
|
+
}
|
|
737
|
+
if (field.validation?.unique) {
|
|
738
|
+
issues.push(cmsIssue({
|
|
739
|
+
code: 'invalid-field-config',
|
|
740
|
+
message: `Component prop "${field.name}" declares "unique", which only applies across a collection's documents.`,
|
|
741
|
+
path: ['props', field.name],
|
|
742
|
+
meta: { field: field.name },
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
// EC-253: a nested object prop's subfields are props too — lint them as well, so
|
|
746
|
+
// a `filterable`/`unique` buried inside a repeater is surfaced, not silently kept.
|
|
747
|
+
if (field.type === 'object') {
|
|
748
|
+
for (const sub of field.fields)
|
|
749
|
+
issues.push(...assertPropFieldDef(sub));
|
|
750
|
+
}
|
|
751
|
+
return issues;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Validate a component's static prop values against its prop field-defs (EC-190).
|
|
755
|
+
* The prop-side mirror of `validateDocument`: the same per-field type / constraint
|
|
756
|
+
* / relation-structure / asset-structure / blocks checks, keyed by prop name,
|
|
757
|
+
* MINUS collection-scoped concerns (uniqueness, filterability, localization) and
|
|
758
|
+
* store-backed existence (relation/asset target *existence* is a where-used /
|
|
759
|
+
* reference-integrity concern handled in EC-190 slice 6). Returns structured
|
|
760
|
+
* issues; never throws. The inspector surfaces these inline exactly like document
|
|
761
|
+
* field issues.
|
|
762
|
+
*/
|
|
763
|
+
/**
|
|
764
|
+
* Validate one prop value against its field-def, recursing into `object` subfields
|
|
765
|
+
* (EC-253). The prop-side mirror of {@link validateFieldValue}: pure type +
|
|
766
|
+
* constraint + relation/asset-structure + composition checks, keyed by `path`.
|
|
767
|
+
* Shared by the top-level loop and the object recursion so a repeater item's
|
|
768
|
+
* subfields are validated exactly as a top-level prop would be.
|
|
769
|
+
*/
|
|
770
|
+
function validatePropFieldValue(field, value, path, options, issues) {
|
|
771
|
+
const push = (detail, code = 'invalid-field-value') => issues.push(cmsIssue({ code, message: `Prop "${field.name}" ${detail}.`, path, meta: { field: field.name } }));
|
|
772
|
+
if (isEmpty(value)) {
|
|
773
|
+
if (field.validation?.required) {
|
|
774
|
+
issues.push(cmsIssue({
|
|
775
|
+
code: 'missing-required-field',
|
|
776
|
+
message: `Required prop "${field.name}" is missing.`,
|
|
777
|
+
path,
|
|
778
|
+
meta: { field: field.name },
|
|
779
|
+
}));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
// EC-253 (C1): an optional but PRESENT object prop still recurses so its own
|
|
783
|
+
// required subfields surface; any other empty/absent value has nothing to check.
|
|
784
|
+
if (!isPresentObjectValue(field, value))
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const typeErr = checkScalarType(field, value);
|
|
788
|
+
if (typeErr)
|
|
789
|
+
push(typeErr);
|
|
790
|
+
for (const detail of checkConstraints(field, value))
|
|
791
|
+
push(detail);
|
|
792
|
+
if (field.type === 'relation') {
|
|
793
|
+
for (const detail of checkRelationStructure(field, value))
|
|
794
|
+
push(detail);
|
|
795
|
+
}
|
|
796
|
+
else if (field.type === 'asset') {
|
|
797
|
+
for (const detail of checkAssetStructure(field, value))
|
|
798
|
+
push(detail);
|
|
799
|
+
}
|
|
800
|
+
else if (field.type === 'blocks' && options.validateComposition) {
|
|
801
|
+
for (const finding of options.validateComposition(value, field)) {
|
|
802
|
+
issues.push(cmsIssue({
|
|
803
|
+
code: 'invalid-field-value',
|
|
804
|
+
severity: finding.severity ?? 'error',
|
|
805
|
+
message: `Prop "${field.name}": ${finding.message}`,
|
|
806
|
+
path,
|
|
807
|
+
meta: {
|
|
808
|
+
field: field.name,
|
|
809
|
+
type: 'blocks',
|
|
810
|
+
...(finding.nodePath ? { nodePath: finding.nodePath } : {}),
|
|
811
|
+
},
|
|
812
|
+
}));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else if (field.type === 'richText' && options.validateRichTextEmbeds) {
|
|
816
|
+
// EC-189: validate the composition embeds carried by a richText prop value
|
|
817
|
+
// (e.g. the RichText primitive's `content`) against the prop's `allow`.
|
|
818
|
+
for (const finding of options.validateRichTextEmbeds(value, field)) {
|
|
819
|
+
issues.push(cmsIssue({
|
|
820
|
+
code: 'invalid-field-value',
|
|
821
|
+
severity: finding.severity ?? 'error',
|
|
822
|
+
message: `Prop "${field.name}": ${finding.message}`,
|
|
823
|
+
path,
|
|
824
|
+
meta: {
|
|
825
|
+
field: field.name,
|
|
826
|
+
type: 'richText',
|
|
827
|
+
...(finding.nodePath ? { nodePath: finding.nodePath } : {}),
|
|
828
|
+
},
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
else if (field.type === 'object') {
|
|
833
|
+
// EC-253: recurse into each item's subfields, mirroring the document path.
|
|
834
|
+
const items = field.cardinality === 'many' ? (Array.isArray(value) ? value : []) : [value];
|
|
835
|
+
items.forEach((item, index) => {
|
|
836
|
+
if (!isPlainObject(item))
|
|
837
|
+
return;
|
|
838
|
+
const itemPath = field.cardinality === 'many' ? [...path, index] : path;
|
|
839
|
+
for (const sub of field.fields) {
|
|
840
|
+
validatePropFieldValue(sub, item[sub.name], [...itemPath, sub.name], options, issues);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
export function validateComponentProps(fieldDefs, props, options = {}) {
|
|
846
|
+
const issues = [];
|
|
847
|
+
for (const field of fieldDefs) {
|
|
848
|
+
issues.push(...assertPropFieldDef(field));
|
|
849
|
+
validatePropFieldValue(field, props[field.name], ['props', field.name], options, issues);
|
|
850
|
+
}
|
|
851
|
+
return issues;
|
|
852
|
+
}
|
|
853
|
+
/** Resolve a relation value into the referenced documents (EC-017). */
|
|
854
|
+
export function resolveRelation(store, value) {
|
|
855
|
+
const raws = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
|
|
856
|
+
const out = [];
|
|
857
|
+
for (const raw of raws) {
|
|
858
|
+
const ref = coerceRef(raw);
|
|
859
|
+
if (!ref)
|
|
860
|
+
continue;
|
|
861
|
+
const doc = store.get(ref);
|
|
862
|
+
if (doc)
|
|
863
|
+
out.push(doc);
|
|
864
|
+
}
|
|
865
|
+
return out;
|
|
866
|
+
}
|
|
867
|
+
/** Build a flat values bag (non-localized only) — convenience for tests/consumers. */
|
|
868
|
+
export function plainValues(doc) {
|
|
869
|
+
return { ...doc.values };
|
|
870
|
+
}
|
|
871
|
+
//# sourceMappingURL=validate-document.js.map
|