@terreno/blocks 57.10.1

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/biome.jsonc +18 -0
  4. package/bunfig.toml +6 -0
  5. package/dist/asks/errors.d.ts +77 -0
  6. package/dist/asks/errors.js +364 -0
  7. package/dist/asks/files.d.ts +53 -0
  8. package/dist/asks/files.js +171 -0
  9. package/dist/asks/formValues.d.ts +32 -0
  10. package/dist/asks/formValues.js +229 -0
  11. package/dist/asks/headless.d.ts +227 -0
  12. package/dist/asks/headless.js +94 -0
  13. package/dist/asks/jsonSchema.d.ts +6 -0
  14. package/dist/asks/jsonSchema.js +62 -0
  15. package/dist/asks/limits.d.ts +58 -0
  16. package/dist/asks/limits.js +61 -0
  17. package/dist/asks/prompt.d.ts +10 -0
  18. package/dist/asks/prompt.js +144 -0
  19. package/dist/asks/schema.d.ts +826 -0
  20. package/dist/asks/schema.js +864 -0
  21. package/dist/asks/simpleCard.d.ts +85 -0
  22. package/dist/asks/simpleCard.js +279 -0
  23. package/dist/asks/validateInput.d.ts +13 -0
  24. package/dist/asks/validateInput.js +19 -0
  25. package/dist/asks/validateResponse.d.ts +13 -0
  26. package/dist/asks/validateResponse.js +278 -0
  27. package/dist/cli.d.ts +11 -0
  28. package/dist/cli.js +58 -0
  29. package/dist/errors.d.ts +51 -0
  30. package/dist/errors.js +106 -0
  31. package/dist/index.d.ts +31 -0
  32. package/dist/index.js +111 -0
  33. package/dist/jsonSchema.d.ts +5 -0
  34. package/dist/jsonSchema.js +12 -0
  35. package/dist/limits.d.ts +27 -0
  36. package/dist/limits.js +30 -0
  37. package/dist/lint.d.ts +21 -0
  38. package/dist/lint.js +388 -0
  39. package/dist/parse.d.ts +16 -0
  40. package/dist/parse.js +95 -0
  41. package/dist/parsePartial.d.ts +12 -0
  42. package/dist/parsePartial.js +105 -0
  43. package/dist/prompt.d.ts +9 -0
  44. package/dist/prompt.js +95 -0
  45. package/dist/schema.d.ts +199 -0
  46. package/dist/schema.js +277 -0
  47. package/dist/validate.d.ts +17 -0
  48. package/dist/validate.js +331 -0
  49. package/package.json +60 -0
  50. package/schemas/pendingAsksResponse.schema.json +195 -0
  51. package/schemas/simpleCard.schema.json +135 -0
  52. package/schemas/turnRequest.schema.json +108 -0
  53. package/schemas/turnResponse.schema.json +204 -0
  54. package/scripts/writeJsonSchemas.ts +11 -0
  55. package/src/asks/docParity.test.ts +65 -0
  56. package/src/asks/errors.test.ts +250 -0
  57. package/src/asks/errors.ts +449 -0
  58. package/src/asks/files.test.ts +294 -0
  59. package/src/asks/files.ts +216 -0
  60. package/src/asks/fixtures/invalid/choice-blank-text.json +20 -0
  61. package/src/asks/fixtures/invalid/choice-default-not-in-options.json +13 -0
  62. package/src/asks/fixtures/invalid/choice-default-selects-two.json +16 -0
  63. package/src/asks/fixtures/invalid/choice-description-too-long.json +24 -0
  64. package/src/asks/fixtures/invalid/choice-duplicate-ids.json +17 -0
  65. package/src/asks/fixtures/invalid/choice-label-too-long.json +23 -0
  66. package/src/asks/fixtures/invalid/choice-many-bounds-not-whole.json +17 -0
  67. package/src/asks/fixtures/invalid/choice-many-bounds-out-of-range.json +17 -0
  68. package/src/asks/fixtures/invalid/choice-many-default-duplicate.json +17 -0
  69. package/src/asks/fixtures/invalid/choice-many-default-too-many.json +15 -0
  70. package/src/asks/fixtures/invalid/choice-many-max-above-options.json +14 -0
  71. package/src/asks/fixtures/invalid/choice-many-min-above-choices.json +13 -0
  72. package/src/asks/fixtures/invalid/choice-many-min-above-max.json +15 -0
  73. package/src/asks/fixtures/invalid/choice-missing-required.json +14 -0
  74. package/src/asks/fixtures/invalid/choice-mixed-errors.json +19 -0
  75. package/src/asks/fixtures/invalid/choice-not-an-object.json +5 -0
  76. package/src/asks/fixtures/invalid/choice-one-with-many-fields.json +19 -0
  77. package/src/asks/fixtures/invalid/choice-option-id-format.json +35 -0
  78. package/src/asks/fixtures/invalid/choice-options-not-objects.json +12 -0
  79. package/src/asks/fixtures/invalid/choice-other-label-too-long.json +25 -0
  80. package/src/asks/fixtures/invalid/choice-other-label-without-allow-other.json +14 -0
  81. package/src/asks/fixtures/invalid/choice-prompt-empty.json +12 -0
  82. package/src/asks/fixtures/invalid/choice-prompt-too-long.json +23 -0
  83. package/src/asks/fixtures/invalid/choice-select-unknown.json +12 -0
  84. package/src/asks/fixtures/invalid/choice-submit-label-too-long.json +13 -0
  85. package/src/asks/fixtures/invalid/choice-title-too-long.json +24 -0
  86. package/src/asks/fixtures/invalid/choice-too-few-options.json +9 -0
  87. package/src/asks/fixtures/invalid/choice-too-many-options.json +219 -0
  88. package/src/asks/fixtures/invalid/choice-unknown-key-option.json +15 -0
  89. package/src/asks/fixtures/invalid/choice-unknown-key-root.json +13 -0
  90. package/src/asks/fixtures/invalid/choice-wrong-types.json +16 -0
  91. package/src/asks/fixtures/invalid/confirm-emoji-label-too-long.json +13 -0
  92. package/src/asks/fixtures/invalid/confirm-label-too-long.json +14 -0
  93. package/src/asks/fixtures/invalid/confirm-labels-alike-after-trim.json +14 -0
  94. package/src/asks/fixtures/invalid/confirm-labels-alike.json +13 -0
  95. package/src/asks/fixtures/invalid/confirm-missing-prompt-blank-label.json +16 -0
  96. package/src/asks/fixtures/invalid/confirm-unknown-keys.json +23 -0
  97. package/src/asks/fixtures/invalid/confirm-wrong-types.json +18 -0
  98. package/src/asks/fixtures/invalid/files-accept-duplicate.json +13 -0
  99. package/src/asks/fixtures/invalid/files-accept-empty.json +13 -0
  100. package/src/asks/fixtures/invalid/files-accept-unknown.json +13 -0
  101. package/src/asks/fixtures/invalid/files-counts-out-of-range.json +19 -0
  102. package/src/asks/fixtures/invalid/files-min-above-max.json +15 -0
  103. package/src/asks/fixtures/invalid/files-missing-accept-unknown-key.json +17 -0
  104. package/src/asks/fixtures/invalid/form-default-bad-format.json +40 -0
  105. package/src/asks/fixtures/invalid/form-default-invalid-date.json +40 -0
  106. package/src/asks/fixtures/invalid/form-default-not-in-options.json +58 -0
  107. package/src/asks/fixtures/invalid/form-default-out-of-range.json +33 -0
  108. package/src/asks/fixtures/invalid/form-default-text-bounds.json +42 -0
  109. package/src/asks/fixtures/invalid/form-default-wrong-type.json +30 -0
  110. package/src/asks/fixtures/invalid/form-duplicate-field-ids.json +24 -0
  111. package/src/asks/fixtures/invalid/form-field-id-format.json +19 -0
  112. package/src/asks/fixtures/invalid/form-field-text-limits.json +33 -0
  113. package/src/asks/fixtures/invalid/form-keys-of-another-type.json +39 -0
  114. package/src/asks/fixtures/invalid/form-length-above-cap.json +30 -0
  115. package/src/asks/fixtures/invalid/form-min-above-max.json +32 -0
  116. package/src/asks/fixtures/invalid/form-no-fields.json +13 -0
  117. package/src/asks/fixtures/invalid/form-select-options.json +44 -0
  118. package/src/asks/fixtures/invalid/form-too-many-fields.json +59 -0
  119. package/src/asks/fixtures/invalid/form-unknown-field-type.json +27 -0
  120. package/src/asks/fixtures/invalid/markdown-bounds-out-of-range.json +18 -0
  121. package/src/asks/fixtures/invalid/markdown-initial-above-cap.json +13 -0
  122. package/src/asks/fixtures/invalid/markdown-max-above-cap.json +13 -0
  123. package/src/asks/fixtures/invalid/markdown-min-above-cap.json +13 -0
  124. package/src/asks/fixtures/invalid/markdown-min-above-max.json +14 -0
  125. package/src/asks/fixtures/invalid/markdown-placeholder-too-long.json +13 -0
  126. package/src/asks/fixtures/invalid/markdown-wrong-types-unknown-key.json +27 -0
  127. package/src/asks/fixtures/valid/choice-emoji-label-cut.json +32 -0
  128. package/src/asks/fixtures/valid/choice-labels-collide-after-cut.json +21 -0
  129. package/src/asks/fixtures/valid/choice-long-text-cut.json +41 -0
  130. package/src/asks/fixtures/valid/choice-many-default-below-min.json +24 -0
  131. package/src/asks/fixtures/valid/choice-many-every-choice-with-other.json +24 -0
  132. package/src/asks/fixtures/valid/choice-many-no-default.json +21 -0
  133. package/src/asks/fixtures/valid/choice-many-optional-no-buttons.json +20 -0
  134. package/src/asks/fixtures/valid/choice-many-options-default-label-collides.json +23 -0
  135. package/src/asks/fixtures/valid/choice-many-options-no-buttons.json +21 -0
  136. package/src/asks/fixtures/valid/choice-many-options-no-default.json +22 -0
  137. package/src/asks/fixtures/valid/choice-many-options-with-default.json +30 -0
  138. package/src/asks/fixtures/valid/choice-many-required-with-default.json +29 -0
  139. package/src/asks/fixtures/valid/choice-many-single-pick-or-other.json +29 -0
  140. package/src/asks/fixtures/valid/choice-many-toppings-with-default.json +36 -0
  141. package/src/asks/fixtures/valid/choice-max-limits.json +245 -0
  142. package/src/asks/fixtures/valid/choice-plan-with-default.json +42 -0
  143. package/src/asks/fixtures/valid/choice-two-options-no-decline.json +32 -0
  144. package/src/asks/fixtures/valid/choice-two-options.json +32 -0
  145. package/src/asks/fixtures/valid/confirm-allow-decline.json +40 -0
  146. package/src/asks/fixtures/valid/confirm-default-labels.json +36 -0
  147. package/src/asks/fixtures/valid/confirm-destructive-delete.json +41 -0
  148. package/src/asks/fixtures/valid/confirm-emoji-labels.json +38 -0
  149. package/src/asks/fixtures/valid/confirm-long-text-cut.json +40 -0
  150. package/src/asks/fixtures/valid/files-every-type.json +28 -0
  151. package/src/asks/fixtures/valid/files-one-csv-no-decline.json +16 -0
  152. package/src/asks/fixtures/valid/files-receipt-photos.json +24 -0
  153. package/src/asks/fixtures/valid/form-boolean-newsletter.json +30 -0
  154. package/src/asks/fixtures/valid/form-date-due.json +44 -0
  155. package/src/asks/fixtures/valid/form-datetime-meeting.json +44 -0
  156. package/src/asks/fixtures/valid/form-datetime-minutes-offset.json +44 -0
  157. package/src/asks/fixtures/valid/form-eight-fields.json +71 -0
  158. package/src/asks/fixtures/valid/form-email-contact.json +44 -0
  159. package/src/asks/fixtures/valid/form-invoice-details.json +68 -0
  160. package/src/asks/fixtures/valid/form-multiselect-channels.json +64 -0
  161. package/src/asks/fixtures/valid/form-number-budget.json +45 -0
  162. package/src/asks/fixtures/valid/form-phone-callback.json +44 -0
  163. package/src/asks/fixtures/valid/form-select-shipping.json +58 -0
  164. package/src/asks/fixtures/valid/form-text-required-default.json +47 -0
  165. package/src/asks/fixtures/valid/form-textarea-notes.json +23 -0
  166. package/src/asks/fixtures/valid/form-time-reminder.json +44 -0
  167. package/src/asks/fixtures/valid/form-url-website.json +30 -0
  168. package/src/asks/fixtures/valid/markdown-announcement-draft.json +39 -0
  169. package/src/asks/fixtures/valid/markdown-empty-draft.json +34 -0
  170. package/src/asks/fixtures/valid/markdown-initial-blank-below-min.json +24 -0
  171. package/src/asks/fixtures/valid/markdown-initial-over-max.json +24 -0
  172. package/src/asks/fixtures/valid/markdown-long-text-cut.json +37 -0
  173. package/src/asks/fixtures/valid/markdown-no-decline.json +28 -0
  174. package/src/asks/fixtures/valid/markdown-no-initial-with-min.json +25 -0
  175. package/src/asks/formValues.ts +320 -0
  176. package/src/asks/headless.test.ts +146 -0
  177. package/src/asks/headless.ts +130 -0
  178. package/src/asks/jsonSchema.test.ts +171 -0
  179. package/src/asks/jsonSchema.ts +89 -0
  180. package/src/asks/limits.ts +58 -0
  181. package/src/asks/prompt.test.ts +200 -0
  182. package/src/asks/prompt.ts +173 -0
  183. package/src/asks/schema.test.ts +198 -0
  184. package/src/asks/schema.ts +1203 -0
  185. package/src/asks/simpleCard.test.ts +998 -0
  186. package/src/asks/simpleCard.ts +341 -0
  187. package/src/asks/validateInput.test.ts +875 -0
  188. package/src/asks/validateInput.ts +24 -0
  189. package/src/asks/validateResponse.test.ts +740 -0
  190. package/src/asks/validateResponse.ts +341 -0
  191. package/src/blocksDocParity.test.ts +21 -0
  192. package/src/cli.test.ts +134 -0
  193. package/src/cli.ts +65 -0
  194. package/src/display.test.ts +68 -0
  195. package/src/errors.test.ts +29 -0
  196. package/src/errors.ts +125 -0
  197. package/src/fixtures/invalid/column-not-found.yaml +14 -0
  198. package/src/fixtures/invalid/column-type.yaml +14 -0
  199. package/src/fixtures/invalid/dataset-not-found.yaml +7 -0
  200. package/src/fixtures/invalid/dataset-too-large.yaml +20 -0
  201. package/src/fixtures/invalid/depth-exceeded.yaml +10 -0
  202. package/src/fixtures/invalid/duplicate-id.yaml +6 -0
  203. package/src/fixtures/invalid/invalid-enum.yaml +5 -0
  204. package/src/fixtures/invalid/key-order.yaml +4 -0
  205. package/src/fixtures/invalid/missing-required.yaml +3 -0
  206. package/src/fixtures/invalid/not-a-document.txt +1 -0
  207. package/src/fixtures/invalid/row-arity.yaml +11 -0
  208. package/src/fixtures/invalid/select-target.yaml +12 -0
  209. package/src/fixtures/invalid/table-too-wide.yaml +9 -0
  210. package/src/fixtures/invalid/too-many-points.yaml +8 -0
  211. package/src/fixtures/invalid/unknown-key.yaml +5 -0
  212. package/src/fixtures/invalid/unsupported-version.yaml +4 -0
  213. package/src/fixtures/invalid/yaml-anchor.yaml +6 -0
  214. package/src/fixtures/partial/cut-after-divider.yaml +5 -0
  215. package/src/fixtures/valid/actions.yaml +32 -0
  216. package/src/fixtures/valid/chart.yaml +20 -0
  217. package/src/fixtures/valid/layout.yaml +26 -0
  218. package/src/fixtures/valid/prose.yaml +4 -0
  219. package/src/html.test.ts +52 -0
  220. package/src/index.test.ts +114 -0
  221. package/src/index.ts +175 -0
  222. package/src/jsonSchema.test.ts +33 -0
  223. package/src/jsonSchema.ts +11 -0
  224. package/src/limits.ts +27 -0
  225. package/src/lint.test.ts +289 -0
  226. package/src/lint.ts +484 -0
  227. package/src/parse.test.ts +281 -0
  228. package/src/parse.ts +104 -0
  229. package/src/parsePartial.test.ts +53 -0
  230. package/src/parsePartial.ts +116 -0
  231. package/src/prompt.test.ts +24 -0
  232. package/src/prompt.ts +104 -0
  233. package/src/schema.ts +509 -0
  234. package/src/tests/askFixtures.ts +40 -0
  235. package/src/validate.perf.test.ts +43 -0
  236. package/src/validate.ts +369 -0
  237. package/tsconfig.json +18 -0
package/dist/schema.js ADDED
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapAsTextDocument = exports.blocksSchema = exports.BUTTON_VARIANTS = exports.DATASET_GRAINS = exports.DATASET_COLUMN_TYPES = exports.CHART_HEIGHTS = exports.CHART_KINDS = exports.LAYOUT_BLOCK_TYPES = exports.METRIC_TRENDS = exports.BADGE_STATUSES = exports.CALLOUT_STATUSES = exports.HTML_HEIGHTS = exports.HEADING_SIZES = void 0;
4
+ const zod_1 = require("zod");
5
+ const limits_1 = require("./limits");
6
+ exports.HEADING_SIZES = ["sm", "md", "lg", "xl", "2xl"];
7
+ exports.HTML_HEIGHTS = ["sm", "md", "lg"];
8
+ exports.CALLOUT_STATUSES = ["info", "warning", "alert"];
9
+ exports.BADGE_STATUSES = ["info", "error", "warning", "success", "neutral", "active"];
10
+ exports.METRIC_TRENDS = ["up", "down", "flat"];
11
+ exports.LAYOUT_BLOCK_TYPES = ["columns", "card"];
12
+ const visibleText = (maxLength) => zod_1.z
13
+ .string()
14
+ .min(1)
15
+ .max(maxLength)
16
+ .refine((value) => value.trim().length > 0, {
17
+ message: "Must contain visible text, not only whitespace.",
18
+ params: { blockCode: "TOO_SHORT" },
19
+ });
20
+ const blockIdSchema = zod_1.z
21
+ .string()
22
+ .regex(/^[a-z][a-z0-9_]{0,63}$/)
23
+ .describe("Optional id. Lowercase letters, digits, and underscores, starting with a letter.");
24
+ const sharedBlockFields = {
25
+ id: blockIdSchema.optional(),
26
+ };
27
+ exports.CHART_KINDS = ["line", "bar", "area", "donut"];
28
+ exports.CHART_HEIGHTS = ["sm", "md", "lg"];
29
+ exports.DATASET_COLUMN_TYPES = ["string", "number", "date"];
30
+ exports.DATASET_GRAINS = ["hour", "day", "week", "month"];
31
+ exports.BUTTON_VARIANTS = ["primary", "secondary", "outline", "ghost", "destructive"];
32
+ const headingSchema = zod_1.z
33
+ .object({
34
+ ...sharedBlockFields,
35
+ size: zod_1.z.enum(exports.HEADING_SIZES).optional(),
36
+ text: visibleText(limits_1.BLOCK_LIMITS.headingTextMaxLength),
37
+ type: zod_1.z.literal("heading"),
38
+ })
39
+ .strict();
40
+ const textSchema = zod_1.z
41
+ .object({
42
+ ...sharedBlockFields,
43
+ markdown: visibleText(limits_1.BLOCK_LIMITS.blockTextMaxLength),
44
+ type: zod_1.z.literal("text"),
45
+ })
46
+ .strict();
47
+ const metricSchema = zod_1.z
48
+ .object({
49
+ ...sharedBlockFields,
50
+ delta: visibleText(limits_1.BLOCK_LIMITS.metricDeltaMaxLength).optional(),
51
+ helper: visibleText(limits_1.BLOCK_LIMITS.metricHelperMaxLength).optional(),
52
+ label: visibleText(limits_1.BLOCK_LIMITS.metricLabelMaxLength),
53
+ trend: zod_1.z.enum(exports.METRIC_TRENDS).optional(),
54
+ type: zod_1.z.literal("metric"),
55
+ value: visibleText(limits_1.BLOCK_LIMITS.metricValueMaxLength),
56
+ })
57
+ .strict();
58
+ const badgeSchema = zod_1.z
59
+ .object({
60
+ ...sharedBlockFields,
61
+ status: zod_1.z.enum(exports.BADGE_STATUSES).optional(),
62
+ text: visibleText(limits_1.BLOCK_LIMITS.badgeTextMaxLength),
63
+ type: zod_1.z.literal("badge"),
64
+ })
65
+ .strict();
66
+ const dividerSchema = zod_1.z
67
+ .object({
68
+ ...sharedBlockFields,
69
+ type: zod_1.z.literal("divider"),
70
+ })
71
+ .strict();
72
+ const contextSchema = zod_1.z
73
+ .object({
74
+ ...sharedBlockFields,
75
+ text: visibleText(limits_1.BLOCK_LIMITS.contextTextMaxLength),
76
+ type: zod_1.z.literal("context"),
77
+ })
78
+ .strict();
79
+ const chartSchema = zod_1.z
80
+ .object({
81
+ ...sharedBlockFields,
82
+ data: zod_1.z.string().min(1).max(64).optional(),
83
+ emptyText: visibleText(limits_1.BLOCK_LIMITS.contextTextMaxLength).optional(),
84
+ height: zod_1.z.enum(exports.CHART_HEIGHTS).optional(),
85
+ kind: zod_1.z.enum(exports.CHART_KINDS),
86
+ legend: zod_1.z.boolean().optional(),
87
+ points: zod_1.z
88
+ .array(zod_1.z
89
+ .object({
90
+ label: visibleText(limits_1.BLOCK_LIMITS.metricLabelMaxLength),
91
+ value: zod_1.z.number().finite(),
92
+ })
93
+ .strict())
94
+ .max(limits_1.BLOCK_LIMITS.datasetRowMax)
95
+ .optional(),
96
+ title: visibleText(limits_1.BLOCK_LIMITS.cardTitleMaxLength).optional(),
97
+ type: zod_1.z.literal("chart"),
98
+ x: zod_1.z.string().min(1).max(64).optional(),
99
+ y: zod_1.z.string().min(1).max(64).optional(),
100
+ })
101
+ .strict();
102
+ const tableSchema = zod_1.z
103
+ .object({
104
+ ...sharedBlockFields,
105
+ columns: zod_1.z.array(zod_1.z.string().min(1).max(64)).min(1).optional(),
106
+ data: zod_1.z.string().min(1).max(64),
107
+ title: visibleText(limits_1.BLOCK_LIMITS.cardTitleMaxLength).optional(),
108
+ type: zod_1.z.literal("table"),
109
+ })
110
+ .strict();
111
+ const replyActionSchema = zod_1.z
112
+ .object({
113
+ kind: zod_1.z.literal("reply"),
114
+ text: visibleText(limits_1.BLOCK_LIMITS.blockTextMaxLength),
115
+ })
116
+ .strict();
117
+ const openActionSchema = zod_1.z
118
+ .object({
119
+ kind: zod_1.z.literal("open"),
120
+ route: zod_1.z.string().min(1).max(200).optional(),
121
+ url: zod_1.z.string().min(1).max(2_000).optional(),
122
+ })
123
+ .strict();
124
+ const selectActionSchema = zod_1.z
125
+ .object({
126
+ data: zod_1.z.string().min(1).max(64),
127
+ kind: zod_1.z.literal("select"),
128
+ target: zod_1.z.string().min(1).max(64),
129
+ })
130
+ .strict();
131
+ const callbackActionSchema = zod_1.z
132
+ .object({
133
+ kind: zod_1.z.literal("callback"),
134
+ name: zod_1.z.string().regex(/^[a-z][A-Za-z0-9_]{0,63}$/),
135
+ payload: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
136
+ })
137
+ .strict();
138
+ const actionSchema = zod_1.z.discriminatedUnion("kind", [
139
+ replyActionSchema,
140
+ openActionSchema,
141
+ selectActionSchema,
142
+ callbackActionSchema,
143
+ ]);
144
+ const buttonElementSchema = zod_1.z
145
+ .object({
146
+ action: actionSchema,
147
+ iconName: zod_1.z.string().min(1).max(40).optional(),
148
+ id: blockIdSchema,
149
+ text: visibleText(80),
150
+ type: zod_1.z.literal("button"),
151
+ variant: zod_1.z.enum(exports.BUTTON_VARIANTS).optional(),
152
+ })
153
+ .strict();
154
+ const segmentedElementSchema = zod_1.z
155
+ .object({
156
+ id: blockIdSchema,
157
+ options: zod_1.z
158
+ .array(zod_1.z
159
+ .object({
160
+ data: zod_1.z.string().min(1).max(64),
161
+ label: visibleText(40),
162
+ })
163
+ .strict())
164
+ .min(2)
165
+ .max(8),
166
+ target: zod_1.z.string().min(1).max(64),
167
+ type: zod_1.z.literal("segmented"),
168
+ })
169
+ .strict();
170
+ const htmlSchema = zod_1.z
171
+ .object({
172
+ ...sharedBlockFields,
173
+ height: zod_1.z.enum(exports.HTML_HEIGHTS).optional(),
174
+ html: zod_1.z.string().min(1),
175
+ title: visibleText(limits_1.BLOCK_LIMITS.headingTextMaxLength).optional(),
176
+ type: zod_1.z.literal("html"),
177
+ })
178
+ .strict();
179
+ const calloutSchema = zod_1.z
180
+ .object({
181
+ ...sharedBlockFields,
182
+ status: zod_1.z.enum(exports.CALLOUT_STATUSES).optional(),
183
+ text: visibleText(limits_1.BLOCK_LIMITS.blockTextMaxLength),
184
+ type: zod_1.z.literal("callout"),
185
+ })
186
+ .strict();
187
+ const imageSchema = zod_1.z
188
+ .object({
189
+ ...sharedBlockFields,
190
+ alt: visibleText(limits_1.BLOCK_LIMITS.headingTextMaxLength),
191
+ src: zod_1.z.string().min(1).max(limits_1.BLOCK_LIMITS.htmlMaxBytes),
192
+ type: zod_1.z.literal("image"),
193
+ })
194
+ .strict();
195
+ const detailsSchema = zod_1.z
196
+ .object({
197
+ ...sharedBlockFields,
198
+ text: visibleText(limits_1.BLOCK_LIMITS.blockTextMaxLength),
199
+ title: visibleText(limits_1.BLOCK_LIMITS.headingTextMaxLength),
200
+ type: zod_1.z.literal("details"),
201
+ })
202
+ .strict();
203
+ const actionsSchema = zod_1.z
204
+ .object({
205
+ elements: zod_1.z
206
+ .array(zod_1.z.discriminatedUnion("type", [buttonElementSchema, segmentedElementSchema]))
207
+ .min(1)
208
+ .max(limits_1.BLOCK_LIMITS.actionElementsMax),
209
+ id: blockIdSchema,
210
+ type: zod_1.z.literal("actions"),
211
+ })
212
+ .strict();
213
+ const blockSchema = zod_1.z.lazy(() => zod_1.z.discriminatedUnion("type", [
214
+ headingSchema,
215
+ textSchema,
216
+ metricSchema,
217
+ badgeSchema,
218
+ dividerSchema,
219
+ contextSchema,
220
+ chartSchema,
221
+ tableSchema,
222
+ actionsSchema,
223
+ htmlSchema,
224
+ calloutSchema,
225
+ imageSchema,
226
+ detailsSchema,
227
+ zod_1.z
228
+ .object({
229
+ ...sharedBlockFields,
230
+ children: zod_1.z.array(blockSchema).min(limits_1.BLOCK_LIMITS.columnsMin).max(limits_1.BLOCK_LIMITS.columnsMax),
231
+ type: zod_1.z.literal("columns"),
232
+ })
233
+ .strict(),
234
+ zod_1.z
235
+ .object({
236
+ ...sharedBlockFields,
237
+ children: zod_1.z.array(blockSchema).min(1).max(limits_1.BLOCK_LIMITS.maxBlocks),
238
+ title: visibleText(limits_1.BLOCK_LIMITS.cardTitleMaxLength).optional(),
239
+ type: zod_1.z.literal("card"),
240
+ })
241
+ .strict(),
242
+ ]));
243
+ const datasetColumnSchema = zod_1.z
244
+ .object({
245
+ name: zod_1.z.string().regex(/^[a-z][a-z0-9_]{0,63}$/),
246
+ type: zod_1.z.enum(exports.DATASET_COLUMN_TYPES),
247
+ })
248
+ .strict();
249
+ const inlineDatasetSchema = zod_1.z
250
+ .object({
251
+ columns: zod_1.z.array(datasetColumnSchema).min(1),
252
+ rows: zod_1.z.array(zod_1.z.array(zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.null()]))),
253
+ source: zod_1.z.literal("inline").optional(),
254
+ })
255
+ .strict();
256
+ const refDatasetSchema = zod_1.z
257
+ .object({
258
+ grain: zod_1.z.enum(exports.DATASET_GRAINS).optional(),
259
+ id: zod_1.z.string().regex(/^[A-Za-z0-9_-]{1,80}$/),
260
+ limit: zod_1.z.number().int().positive().optional(),
261
+ source: zod_1.z.literal("ref"),
262
+ })
263
+ .strict();
264
+ const datasetSchema = zod_1.z.union([refDatasetSchema, inlineDatasetSchema]);
265
+ /** Document schema. Semantic dataset and chart checks run in `lintDocument`. */
266
+ exports.blocksSchema = zod_1.z
267
+ .object({
268
+ blocks: zod_1.z.array(blockSchema).min(1).max(limits_1.BLOCK_LIMITS.maxBlocks),
269
+ datasets: zod_1.z.record(zod_1.z.string().regex(/^[a-z][a-z0-9_]{0,63}$/), datasetSchema).optional(),
270
+ v: zod_1.z.literal(1),
271
+ })
272
+ .strict();
273
+ const wrapAsTextDocument = (text) => ({
274
+ blocks: [{ markdown: text, type: "text" }],
275
+ v: 1,
276
+ });
277
+ exports.wrapAsTextDocument = wrapAsTextDocument;
@@ -0,0 +1,17 @@
1
+ import { type BlockError } from "./errors";
2
+ import { type LintBlocksOptions } from "./lint";
3
+ import { type BlocksDocument } from "./schema";
4
+ export type ValidateBlocksResult = {
5
+ doc: BlocksDocument;
6
+ ok: true;
7
+ warnings: BlockError[];
8
+ } | {
9
+ errors: BlockError[];
10
+ ok: false;
11
+ warnings: BlockError[];
12
+ };
13
+ /**
14
+ * Checks structure, then dataset, chart, and table lint.
15
+ * Returns every error at once. Warnings do not block a valid document.
16
+ */
17
+ export declare const validateBlocks: (doc: unknown, options?: LintBlocksOptions) => ValidateBlocksResult;
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateBlocks = void 0;
4
+ const errors_1 = require("./errors");
5
+ const limits_1 = require("./limits");
6
+ const lint_1 = require("./lint");
7
+ const schema_1 = require("./schema");
8
+ const TOP_LEVEL_ORDER = ["v", "datasets", "blocks"];
9
+ const layoutTypes = new Set(schema_1.LAYOUT_BLOCK_TYPES);
10
+ const error = ({ code, fix, message, path, }) => ({ code, fix, message, path });
11
+ const isPlainMapping = (value) => {
12
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
13
+ return false;
14
+ }
15
+ return Object.getPrototypeOf(value) === Object.prototype;
16
+ };
17
+ const keyOrderError = (value) => {
18
+ const present = Object.keys(value).filter((key) => TOP_LEVEL_ORDER.some((ordered) => ordered === key));
19
+ const expected = TOP_LEVEL_ORDER.filter((key) => present.includes(key));
20
+ for (let index = 0; index < present.length; index += 1) {
21
+ if (present[index] !== expected[index]) {
22
+ return error({
23
+ code: "KEY_ORDER",
24
+ fix: "Put the keys in this order: v, datasets, blocks.",
25
+ message: errors_1.BLOCK_ERROR_CODES.KEY_ORDER,
26
+ path: present[index] ?? "",
27
+ });
28
+ }
29
+ }
30
+ return undefined;
31
+ };
32
+ const versionError = (value) => {
33
+ if (!Object.hasOwn(value, "v")) {
34
+ return undefined;
35
+ }
36
+ if (value.v === 1) {
37
+ return undefined;
38
+ }
39
+ return error({
40
+ code: "UNSUPPORTED_VERSION",
41
+ fix: "Set v to 1.",
42
+ message: errors_1.BLOCK_ERROR_CODES.UNSUPPORTED_VERSION,
43
+ path: "v",
44
+ });
45
+ };
46
+ const countBlocks = (value) => {
47
+ if (!isPlainMapping(value) || typeof value.type !== "string") {
48
+ return 0;
49
+ }
50
+ let count = 1;
51
+ if (Array.isArray(value.children)) {
52
+ for (const child of value.children) {
53
+ count += countBlocks(child);
54
+ }
55
+ }
56
+ return count;
57
+ };
58
+ const blockCountError = (value) => {
59
+ if (!Array.isArray(value.blocks)) {
60
+ return undefined;
61
+ }
62
+ let count = 0;
63
+ for (const block of value.blocks) {
64
+ count += countBlocks(block);
65
+ }
66
+ if (count <= limits_1.BLOCK_LIMITS.maxBlocks) {
67
+ return undefined;
68
+ }
69
+ return error({
70
+ code: "TOO_MANY_BLOCKS",
71
+ fix: `Use ${limits_1.BLOCK_LIMITS.maxBlocks} blocks or fewer.`,
72
+ message: errors_1.BLOCK_ERROR_CODES.TOO_MANY_BLOCKS,
73
+ path: "blocks",
74
+ });
75
+ };
76
+ const depthPath = (value, depth, path) => {
77
+ if (!isPlainMapping(value)) {
78
+ return undefined;
79
+ }
80
+ const type = value.type;
81
+ const isLayout = typeof type === "string" && layoutTypes.has(type);
82
+ if (isLayout && depth >= limits_1.BLOCK_LIMITS.maxDepth) {
83
+ return path;
84
+ }
85
+ if (!Array.isArray(value.children)) {
86
+ return undefined;
87
+ }
88
+ const childDepth = isLayout ? depth + 1 : depth;
89
+ for (let index = 0; index < value.children.length; index += 1) {
90
+ const found = depthPath(value.children[index], childDepth, `${path}.children[${index}]`);
91
+ if (found !== undefined) {
92
+ return found;
93
+ }
94
+ }
95
+ return undefined;
96
+ };
97
+ const depthError = (value) => {
98
+ if (!Array.isArray(value.blocks)) {
99
+ return undefined;
100
+ }
101
+ for (let index = 0; index < value.blocks.length; index += 1) {
102
+ const path = depthPath(value.blocks[index], 1, `blocks[${index}]`);
103
+ if (path === undefined) {
104
+ continue;
105
+ }
106
+ return error({
107
+ code: "DEPTH_EXCEEDED",
108
+ fix: "Keep columns and card at the top level. Their children must be leaf blocks.",
109
+ message: errors_1.BLOCK_ERROR_CODES.DEPTH_EXCEEDED,
110
+ path,
111
+ });
112
+ }
113
+ return undefined;
114
+ };
115
+ const lookup = (root, path) => {
116
+ let current = root;
117
+ for (const segment of path) {
118
+ if (Array.isArray(current) && typeof segment === "number") {
119
+ current = current[segment];
120
+ continue;
121
+ }
122
+ if (isPlainMapping(current) && (typeof segment === "string" || typeof segment === "number")) {
123
+ current = current[String(segment)];
124
+ continue;
125
+ }
126
+ return undefined;
127
+ }
128
+ return current;
129
+ };
130
+ const subject = (path) => (path === "" ? "The value" : path);
131
+ const mapIssue = (issue, root) => {
132
+ const path = (0, errors_1.formatBlockPath)(issue.path);
133
+ if (issue.code === "unrecognized_keys") {
134
+ return issue.keys.map((key) => error({
135
+ code: "UNKNOWN_KEY",
136
+ fix: `Remove "${key}".`,
137
+ message: `"${key}" is not a field of ${path === "" ? "this object" : path}.`,
138
+ path: path === "" ? key : `${path}.${key}`,
139
+ }));
140
+ }
141
+ if (issue.code === "invalid_value") {
142
+ const received = lookup(root, issue.path);
143
+ return [
144
+ error({
145
+ code: "INVALID_ENUM",
146
+ fix: `Use one of: ${issue.values.map((value) => JSON.stringify(value)).join(", ")}.`,
147
+ message: `${subject(path)} must be one of the allowed values, not ${JSON.stringify(received)}.`,
148
+ path,
149
+ }),
150
+ ];
151
+ }
152
+ if (issue.code === "invalid_type") {
153
+ const received = lookup(root, issue.path);
154
+ if (received === undefined) {
155
+ return [
156
+ error({
157
+ code: "MISSING_REQUIRED",
158
+ fix: `Add ${path === "" ? "the missing field" : path}.`,
159
+ message: errors_1.BLOCK_ERROR_CODES.MISSING_REQUIRED,
160
+ path,
161
+ }),
162
+ ];
163
+ }
164
+ return [
165
+ error({
166
+ code: "INVALID_TYPE",
167
+ fix: `Make ${subject(path)} a ${issue.expected}.`,
168
+ message: `${subject(path)} has the wrong type.`,
169
+ path,
170
+ }),
171
+ ];
172
+ }
173
+ if (issue.code === "too_big") {
174
+ const maximum = String(issue.maximum);
175
+ if (issue.origin === "array" && path === "blocks") {
176
+ return [
177
+ error({
178
+ code: "TOO_MANY_BLOCKS",
179
+ fix: `Use ${maximum} blocks or fewer.`,
180
+ message: errors_1.BLOCK_ERROR_CODES.TOO_MANY_BLOCKS,
181
+ path,
182
+ }),
183
+ ];
184
+ }
185
+ if (issue.origin === "array") {
186
+ return [
187
+ error({
188
+ code: "TOO_MANY",
189
+ fix: `Remove items from ${subject(path)} until it has ${maximum} or fewer.`,
190
+ message: `${subject(path)} has more than ${maximum} items.`,
191
+ path,
192
+ }),
193
+ ];
194
+ }
195
+ return [
196
+ error({
197
+ code: "TOO_LONG",
198
+ fix: `Shorten ${subject(path)} to ${maximum} characters or fewer.`,
199
+ message: `${subject(path)} is longer than ${maximum} characters.`,
200
+ path,
201
+ }),
202
+ ];
203
+ }
204
+ if (issue.code === "too_small") {
205
+ if (issue.origin === "number" || issue.origin === "int") {
206
+ return [
207
+ error({
208
+ code: "INVALID_TYPE",
209
+ fix: `Set ${subject(path)} to an integer of at least ${String(issue.minimum)}.`,
210
+ message: `${subject(path)} must be an integer of at least ${String(issue.minimum)}.`,
211
+ path,
212
+ }),
213
+ ];
214
+ }
215
+ if (issue.origin === "array") {
216
+ return [
217
+ error({
218
+ code: "TOO_FEW",
219
+ fix: `Add items to ${subject(path)} until it has at least ${String(issue.minimum)}.`,
220
+ message: `${subject(path)} has fewer items than allowed.`,
221
+ path,
222
+ }),
223
+ ];
224
+ }
225
+ return [
226
+ error({
227
+ code: "TOO_SHORT",
228
+ fix: `Write visible text for ${subject(path)}.`,
229
+ message: errors_1.BLOCK_ERROR_CODES.TOO_SHORT,
230
+ path,
231
+ }),
232
+ ];
233
+ }
234
+ if (issue.code === "invalid_format") {
235
+ const isRefId = /^datasets\.[^.]+\.id$/.test(path);
236
+ return [
237
+ error({
238
+ code: "INVALID_FORMAT",
239
+ fix: isRefId
240
+ ? "Use 1 to 80 letters, digits, underscores, or hyphens."
241
+ : "Use a lowercase id starting with a letter, then letters, digits, or underscores.",
242
+ message: `${subject(path)} does not match the required format.`,
243
+ path,
244
+ }),
245
+ ];
246
+ }
247
+ if (issue.code === "custom" && issue.params?.blockCode === "TOO_SHORT") {
248
+ return [
249
+ error({
250
+ code: "TOO_SHORT",
251
+ fix: `Write visible text for ${subject(path)}.`,
252
+ message: errors_1.BLOCK_ERROR_CODES.TOO_SHORT,
253
+ path,
254
+ }),
255
+ ];
256
+ }
257
+ if (issue.code === "invalid_union") {
258
+ const nested = "errors" in issue && Array.isArray(issue.errors) ? issue.errors.flat() : [];
259
+ if (nested.length > 0) {
260
+ return nested.flatMap((nestedIssue) => mapIssue(nestedIssue, root));
261
+ }
262
+ return [
263
+ error({
264
+ code: "INVALID_ENUM",
265
+ fix: "Set type to heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details, or html.",
266
+ message: `${subject(path)} is not a supported block.`,
267
+ path: path === "" ? "type" : `${path}.type`,
268
+ }),
269
+ ];
270
+ }
271
+ return [
272
+ error({
273
+ code: "INVALID_TYPE",
274
+ fix: `Check ${subject(path)}.`,
275
+ message: issue.message,
276
+ path,
277
+ }),
278
+ ];
279
+ };
280
+ const dropVersionIssues = (issues) => issues.filter((issue) => !(issue.path.length === 1 && issue.path[0] === "v"));
281
+ /**
282
+ * Checks structure, then dataset, chart, and table lint.
283
+ * Returns every error at once. Warnings do not block a valid document.
284
+ */
285
+ const validateBlocks = (doc, options) => {
286
+ const structural = [];
287
+ if (isPlainMapping(doc)) {
288
+ const order = keyOrderError(doc);
289
+ if (order) {
290
+ structural.push(order);
291
+ }
292
+ const version = versionError(doc);
293
+ if (version) {
294
+ structural.push(version);
295
+ }
296
+ const depth = depthError(doc);
297
+ if (depth) {
298
+ structural.push(depth);
299
+ }
300
+ const count = blockCountError(doc);
301
+ if (count) {
302
+ structural.push(count);
303
+ }
304
+ }
305
+ const parsed = schema_1.blocksSchema.safeParse(doc);
306
+ if (!parsed.success) {
307
+ const issues = structural.some((item) => item.code === "UNSUPPORTED_VERSION")
308
+ ? dropVersionIssues(parsed.error.issues)
309
+ : parsed.error.issues;
310
+ const skipBlockCount = structural.some((item) => item.code === "TOO_MANY_BLOCKS");
311
+ for (const issue of issues) {
312
+ if (skipBlockCount && issue.code === "too_big" && (0, errors_1.formatBlockPath)(issue.path) === "blocks") {
313
+ continue;
314
+ }
315
+ structural.push(...mapIssue(issue, doc));
316
+ }
317
+ }
318
+ if (structural.length > 0 || !parsed.success) {
319
+ return { errors: (0, errors_1.sortBlockErrors)(structural), ok: false, warnings: [] };
320
+ }
321
+ const linted = (0, lint_1.lintDocument)(parsed.data, options);
322
+ if (linted.errors.length > 0) {
323
+ return {
324
+ errors: (0, errors_1.sortBlockErrors)(linted.errors),
325
+ ok: false,
326
+ warnings: (0, errors_1.sortBlockErrors)(linted.warnings),
327
+ };
328
+ }
329
+ return { doc: parsed.data, ok: true, warnings: (0, errors_1.sortBlockErrors)(linted.warnings) };
330
+ };
331
+ exports.validateBlocks = validateBlocks;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "author": "Josh Gachnang",
3
+ "bin": {
4
+ "terreno-blocks": "./dist/cli.js"
5
+ },
6
+ "bugs": {
7
+ "url": "https://github.com/TerrenoLabs/terreno/issues"
8
+ },
9
+ "dependencies": {
10
+ "yaml": "^2.4.0",
11
+ "zod": "^4.4.3"
12
+ },
13
+ "description": "Shared contracts for agent-driven UI: ask schemas, simple cards, and whole-reply block documents — part of Terreno, the batteries-included TypeScript framework for universal apps.",
14
+ "devDependencies": {
15
+ "@biomejs/biome": "^2.3.6",
16
+ "@types/bun": "^1.4.0",
17
+ "ajv": "8.20.0",
18
+ "typescript": "~6.0.3"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "default": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ },
25
+ "./fixtures/*": "./src/asks/fixtures/*",
26
+ "./schemas/*": "./schemas/*"
27
+ },
28
+ "homepage": "https://github.com/TerrenoLabs/terreno#readme",
29
+ "keywords": [
30
+ "ai",
31
+ "agents",
32
+ "generative-ui",
33
+ "zod",
34
+ "terreno"
35
+ ],
36
+ "license": "MIT",
37
+ "main": "dist/index.js",
38
+ "name": "@terreno/blocks",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+ssh://git@github.com/TerrenoLabs/terreno.git"
45
+ },
46
+ "scripts": {
47
+ "compile": "bun tsc",
48
+ "compile:watch": "bun tsc -w",
49
+ "dev": "bun tsc -w",
50
+ "lint": "biome check ./src ./scripts",
51
+ "lint:fix": "biome check --write ./src ./scripts",
52
+ "schemas": "bun scripts/writeJsonSchemas.ts",
53
+ "test": "bun test",
54
+ "test:agent": "AGENT=1 bun test --only-failures",
55
+ "test:ci": "bun test",
56
+ "test:coverage": "bun run ../scripts/check-coverage.ts"
57
+ },
58
+ "types": "dist/index.d.ts",
59
+ "version": "57.10.1"
60
+ }