@avocadostudio-ai/shared 0.2.0 → 0.2.3

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.
@@ -97,10 +97,8 @@ export type BlockRegistration = {
97
97
  schema: z.ZodObject<any>;
98
98
  meta: BlockMeta;
99
99
  };
100
- /**
101
- * Register a block type. Can be called at module load time.
102
- * Re-registering the same type overwrites the previous registration.
103
- */
100
+ /** Test seam: forget what has already been warned about. */
101
+ export declare function resetListFieldWarnings(): void;
104
102
  export declare function registerBlock(type: string, config: BlockRegistration): void;
105
103
  /** Get metadata for a registered block type, or undefined. */
106
104
  export declare function getBlockMeta(type: string): BlockMeta | undefined;
@@ -23,6 +23,74 @@ const _blockMeta = G.__ase_blockMeta ?? (G.__ase_blockMeta = {});
23
23
  * Register a block type. Can be called at module load time.
24
24
  * Re-registering the same type overwrites the previous registration.
25
25
  */
26
+ /*
27
+ * `meta.listFields` and the props schema are two halves of one declaration, and
28
+ * only the schema is load-bearing: the property panel renders rows from the
29
+ * schema's array, and `listFields` only says how to label them. So a list named
30
+ * in the meta and missing from the schema produces no rows, no Add control, and
31
+ * no error — the one outcome that carries no information.
32
+ *
33
+ * That is not hypothetical. An integration declared six accordion items, four
34
+ * contact rows, a price table's tiers and every block's buttons this way, and
35
+ * the whole run passed: validation succeeded, operations applied, the preview
36
+ * rendered, the publisher diffed the rows correctly. The only symptom was an
37
+ * absence in one panel — and it was invisible precisely because the block's
38
+ * schema carried `.catchall(z.unknown())`, which every CMS integration needs
39
+ * because our own publish guidance tells it to stash a `__source` snapshot in
40
+ * block props. The catchall swallowed the undeclared arrays as extras.
41
+ *
42
+ * The loop below was already standing on the contradiction: it walks
43
+ * `listFields` against the schema to derive `required`, finds no array, and
44
+ * steps over it. This is that step, saying so.
45
+ *
46
+ * A warning rather than a throw: `registerBlocks` re-runs on every `/blocks`
47
+ * request, and a registry that can fail a request is worse than a panel with a
48
+ * missing list. Deduped for the same reason.
49
+ */
50
+ /**
51
+ * Strip the wrappers that make a schema optional, so the shape underneath can be
52
+ * inspected. `z.array(z.object({…})).optional()` is a `ZodOptional` holding the
53
+ * array, and reaching for `.element` on the wrapper finds nothing.
54
+ *
55
+ * Follows `def.innerType`, which is what `ZodOptional`, `ZodDefault` and
56
+ * `ZodNullable` carry, and deliberately **not** `.unwrap()`: in Zod 4 an array
57
+ * has that method too and it returns the array's *element*, so a generic
58
+ * unwrapper walks `ZodOptional → ZodArray → ZodObject` and lands one step past
59
+ * the array it was looking for.
60
+ */
61
+ function unwrapZod(schema) {
62
+ let current = schema;
63
+ for (let depth = 0; depth < 5 && current; depth += 1) {
64
+ const inner = current.def?.innerType ?? current._def?.innerType;
65
+ if (!inner)
66
+ return current;
67
+ current = inner;
68
+ }
69
+ return current;
70
+ }
71
+ const warnedListFields = new Set();
72
+ function warnUnbackedListField(type, listKey, present) {
73
+ const key = `${type}.${listKey}`;
74
+ if (warnedListFields.has(key))
75
+ return;
76
+ warnedListFields.add(key);
77
+ const because = present
78
+ ? `the schema's \`${listKey}\` is not an array of objects`
79
+ : `the schema has no \`${listKey}\``;
80
+ try {
81
+ console.warn(`[avocado] ${type}: meta.listFields declares "${listKey}" but ${because}. ` +
82
+ `The property panel renders list rows from the schema, so this list will show ` +
83
+ `no rows and no Add control. Declare it alongside the meta, e.g. ` +
84
+ `${listKey}: z.array(z.object({ … })).optional()`);
85
+ }
86
+ catch {
87
+ // A host with no console is not a reason to fail a registration.
88
+ }
89
+ }
90
+ /** Test seam: forget what has already been warned about. */
91
+ export function resetListFieldWarnings() {
92
+ warnedListFields.clear();
93
+ }
26
94
  export function registerBlock(type, config) {
27
95
  _blockSchemas[type] = config.schema;
28
96
  // Auto-derive `required` on each FieldMeta from the Zod schema shape
@@ -40,10 +108,15 @@ export function registerBlock(type, config) {
40
108
  if (config.meta.listFields) {
41
109
  for (const [listKey, listMeta] of Object.entries(config.meta.listFields)) {
42
110
  const listZod = shape[listKey];
43
- // Unwrap ZodArray → element (ZodObject)
44
- const elementShape = listZod?.element?.shape;
45
- if (!elementShape)
111
+ // Unwrap ZodOptional / ZodDefault / ZodNullable → ZodArray → element.
112
+ // Without the outer unwrap, `z.array(...).optional()` — which is how a
113
+ // list should be declared — looked like no array at all, so every
114
+ // optional list silently skipped its own `required` derivation.
115
+ const elementShape = unwrapZod(listZod)?.element?.shape;
116
+ if (!elementShape) {
117
+ warnUnbackedListField(type, listKey, listZod !== undefined);
46
118
  continue;
119
+ }
47
120
  for (const [itemKey, itemField] of Object.entries(listMeta.itemFields)) {
48
121
  if (itemField.required !== undefined)
49
122
  continue;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ export type { FieldDiffKind, FieldDiff, BlockDiffStatus, BlockDiff, PageDiffStat
4
4
  export { isImagePath, toAltPath, setPropAtPath } from "./editable-path.ts";
5
5
  export { parseInline, parseRichText, parseRichTextBlocks, normalizeRichTextBody, resolveRichTextHeadingLevel, clampMarkdownHeadings, unescapeMarkdownText, isRichTextDoc, fromMarkdown, toMarkdown, mergeRichTextDoc, NODE, MARK, type InlineToken, type RichTextBlock, type RichTextList, type RichTextListItem, type RichTextDoc, type RichTextNode, type RichTextMark } from "@avocadostudio-ai/richtext";
6
6
  export { blockDefinitionSchema, blockManifestSchema, buildBlockManifest, jsonSchemaLikeSchema, validateByJsonSchemaLike, findManifestSchemaIssue, type ManifestSchemaIssue, validateManifestDefaultProps, deriveFieldMetaFromSchema, resolveManifestFieldMeta, isProseMirrorDocSchema, type BlockDefinition, type BlockManifest } from "./block-manifest.ts";
7
- export { type FieldKind, type ImageSpec, type FieldMeta, type ListFieldMeta, type BlockMeta, type BlockType, type BlockInstance, type BlockRegistration, IMAGE_PLACEHOLDER, isImagePlaceholder, registerBlock, getBlockMeta, getAllBlockMeta, getImageFields, getListImageFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes, blockSchemas, allowedBlockTypes, declareBlockCatalogue, getBlockCatalogue, isInBlockCatalogue, catalogueBlockTypes, undeclaredBlockTypes, getPropDisplayName, defaultListItemForBlock, blockInstanceSchema, blockInstanceSchemaLenient, validateBlockProps, getBlockJsonSchema, } from "./blocks/_registry.ts";
7
+ export { z } from "zod";
8
+ export { type FieldKind, type ImageSpec, type FieldMeta, type ListFieldMeta, type BlockMeta, type BlockType, type BlockInstance, type BlockRegistration, IMAGE_PLACEHOLDER, isImagePlaceholder, registerBlock, resetListFieldWarnings, getBlockMeta, getAllBlockMeta, getImageFields, getListImageFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes, blockSchemas, allowedBlockTypes, declareBlockCatalogue, getBlockCatalogue, isInBlockCatalogue, catalogueBlockTypes, undeclaredBlockTypes, getPropDisplayName, defaultListItemForBlock, blockInstanceSchema, blockInstanceSchemaLenient, validateBlockProps, getBlockJsonSchema, } from "./blocks/_registry.ts";
8
9
  export { defaultPropsForType, declaredDefaultPropsForType, resolveHeadingTag, resolveItemHeadingTag, DEFAULT_HEADING_LEVELS, } from "./blocks/index.ts";
9
10
  export { blockTypeToCamel, camelToBlockType, blockTypeToLower, lowerToBlockType, } from "./block-names.ts";
10
11
  export { makeAddBlock, generateBlockId, makeAddItem, generateItemId, ensureItemIds, type AddBlockOp, type MakeAddBlockOptions, type AddItemOp, type MakeAddItemOptions, } from "./ops/builders.ts";
package/dist/index.js CHANGED
@@ -11,11 +11,29 @@ export { isImagePath, toAltPath, setPropAtPath } from "./editable-path.js";
11
11
  */
12
12
  export { parseInline, parseRichText, parseRichTextBlocks, normalizeRichTextBody, resolveRichTextHeadingLevel, clampMarkdownHeadings, unescapeMarkdownText, isRichTextDoc, fromMarkdown, toMarkdown, mergeRichTextDoc, NODE, MARK } from "@avocadostudio-ai/richtext";
13
13
  export { blockDefinitionSchema, blockManifestSchema, buildBlockManifest, jsonSchemaLikeSchema, validateByJsonSchemaLike, findManifestSchemaIssue, validateManifestDefaultProps, deriveFieldMetaFromSchema, resolveManifestFieldMeta, isProseMirrorDocSchema } from "./block-manifest.js";
14
+ /*
15
+ * The exact `zod` instance every schema in this package was built with.
16
+ *
17
+ * `registerBlock` takes a `z.ZodObject`, and a Zod object is only assignable to
18
+ * one produced by the *same* copy of the library. An integrator's own `import
19
+ * { z } from "zod"` resolves to whatever their tree hoisted, which on any site
20
+ * that also uses Sanity is zod 3.x — and the failure is a wall of structural
21
+ * type errors naming methods nobody has heard of:
22
+ *
23
+ * Type 'ZodObject<…, "passthrough", …>' is missing the following properties
24
+ * from type 'ZodObject<any, $strip>': loose, safeExtend, exactPartial, def,
25
+ * and 21 more
26
+ *
27
+ * Nothing in that message says "you have two copies of zod". Re-exporting ours
28
+ * means `import { z } from "@avocadostudio-ai/shared"` cannot pick the wrong
29
+ * one, and the adopter needs no direct zod dependency at all.
30
+ */
31
+ export { z } from "zod";
14
32
  export {
15
33
  // Constants & helpers
16
34
  IMAGE_PLACEHOLDER, isImagePlaceholder,
17
35
  // Registry functions
18
- registerBlock, getBlockMeta, getAllBlockMeta, getImageFields, getListImageFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes,
36
+ registerBlock, resetListFieldWarnings, getBlockMeta, getAllBlockMeta, getImageFields, getListImageFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes,
19
37
  // Backwards-compatible exports
20
38
  blockSchemas, allowedBlockTypes,
21
39
  // The catalogue a site actually renders — see `declareBlockCatalogue`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avocadostudio-ai/shared",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,13 +19,23 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "zod": "^4.3.6",
22
- "@avocadostudio-ai/richtext": "0.2.0"
22
+ "@avocadostudio-ai/richtext": "0.2.3"
23
23
  },
24
24
  "devDependencies": {
25
25
  "tsx": "^4.21.0",
26
26
  "typescript": "^5.7.3"
27
27
  },
28
28
  "description": "Shared Zod schemas, block registry, and operation types for Avocado Studio",
29
+ "keywords": [
30
+ "avocado",
31
+ "avocado-studio",
32
+ "cms",
33
+ "blocks",
34
+ "zod",
35
+ "schema",
36
+ "page-builder",
37
+ "visual-editing"
38
+ ],
29
39
  "license": "Apache-2.0",
30
40
  "homepage": "https://github.com/avocadostudio-ai/avocado/tree/main/packages/shared#readme",
31
41
  "bugs": {