@meissa_a/meissa 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +350 -0
- package/dist/apps/app-generator.d.ts +30 -0
- package/dist/apps/index.d.ts +25 -0
- package/dist/apps/postmessage.d.ts +24 -0
- package/dist/apps/template.d.ts +10 -0
- package/dist/apps/types.d.ts +107 -0
- package/dist/apps.cjs +687 -0
- package/dist/apps.js +645 -0
- package/dist/client/component.d.ts +17 -0
- package/dist/client/content.d.ts +12 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/patch.d.ts +6 -0
- package/dist/client/renderer.d.ts +43 -0
- package/dist/client/types.d.ts +52 -0
- package/dist/client.cjs +3558 -0
- package/dist/client.js +3524 -0
- package/dist/renderer/ComponentRenderer.d.ts +11 -0
- package/dist/renderer/MeissaRenderer.d.ts +8 -0
- package/dist/renderer/components/Alert.d.ts +2 -0
- package/dist/renderer/components/Badge.d.ts +2 -0
- package/dist/renderer/components/Btn.d.ts +2 -0
- package/dist/renderer/components/Card.d.ts +2 -0
- package/dist/renderer/components/Chart.d.ts +2 -0
- package/dist/renderer/components/Code.d.ts +2 -0
- package/dist/renderer/components/Col.d.ts +2 -0
- package/dist/renderer/components/Divider.d.ts +2 -0
- package/dist/renderer/components/Form.d.ts +2 -0
- package/dist/renderer/components/Grid.d.ts +2 -0
- package/dist/renderer/components/Heading.d.ts +2 -0
- package/dist/renderer/components/Img.d.ts +2 -0
- package/dist/renderer/components/Input.d.ts +2 -0
- package/dist/renderer/components/List.d.ts +2 -0
- package/dist/renderer/components/Progress.d.ts +2 -0
- package/dist/renderer/components/Row.d.ts +2 -0
- package/dist/renderer/components/Select.d.ts +2 -0
- package/dist/renderer/components/Split.d.ts +2 -0
- package/dist/renderer/components/Stat.d.ts +2 -0
- package/dist/renderer/components/Table.d.ts +2 -0
- package/dist/renderer/components/Tabs.d.ts +2 -0
- package/dist/renderer/components/Text.d.ts +2 -0
- package/dist/renderer/components/index.d.ts +2 -0
- package/dist/renderer/context/ActionContext.d.ts +3 -0
- package/dist/renderer/context/DataContext.d.ts +3 -0
- package/dist/renderer/context/FormContext.d.ts +10 -0
- package/dist/renderer/core/binder.d.ts +17 -0
- package/dist/renderer/core/evaluator.d.ts +13 -0
- package/dist/renderer/core/iterator.d.ts +10 -0
- package/dist/renderer/core/normalizer.d.ts +5 -0
- package/dist/renderer/core/parser.d.ts +8 -0
- package/dist/renderer/core/resolver.d.ts +6 -0
- package/dist/renderer/core/types.d.ts +68 -0
- package/dist/renderer/index.d.ts +7 -0
- package/dist/renderer/utils/cn.d.ts +2 -0
- package/dist/renderer/utils/filters.d.ts +8 -0
- package/dist/renderer/utils/sanitize.d.ts +9 -0
- package/dist/renderer.cjs +3273 -0
- package/dist/renderer.js +3239 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/markdown.d.ts +9 -0
- package/dist/server/server.d.ts +39 -0
- package/dist/server/types.d.ts +56 -0
- package/dist/server/validator.d.ts +5 -0
- package/dist/server.cjs +345 -0
- package/dist/server.js +303 -0
- package/package.json +84 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { MeissaServer } from "./server.js";
|
|
2
|
+
export { toMarkdown } from "./markdown.js";
|
|
3
|
+
export { validate } from "./validator.js";
|
|
4
|
+
export type { ComponentType, LayoutType, AllComponentTypes, SchemaComponent, Schema, Data, RenderOptions, MeissaContentBlock, MeissaPatchContentBlock, PatchOperation, RenderResponse, PatchResponse, ValidationResult, } from "./types.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Schema, Data } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Convert a Meissa schema + data into a markdown string.
|
|
4
|
+
*/
|
|
5
|
+
export declare function schemaToMarkdown(schema: Schema, data: Data): string;
|
|
6
|
+
/**
|
|
7
|
+
* Public toMarkdown function.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toMarkdown(schema: Schema, data?: Data): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Schema, Data, RenderOptions, RenderResponse, PatchResponse, PatchOperation, ValidationResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* MeissaServer provides methods for MCP server developers to return
|
|
4
|
+
* Meissa UI content from their tools.
|
|
5
|
+
*/
|
|
6
|
+
export declare class MeissaServer {
|
|
7
|
+
/**
|
|
8
|
+
* Render a Meissa UI schema with data into an MCP content response.
|
|
9
|
+
*
|
|
10
|
+
* @param schema - Array of Meissa UI components
|
|
11
|
+
* @param data - Data object for binding resolution
|
|
12
|
+
* @param opts - Optional render options (fallback override, theme)
|
|
13
|
+
* @returns MCP content response with type "meissa"
|
|
14
|
+
*/
|
|
15
|
+
render(schema: Schema, data?: Data, opts?: RenderOptions): RenderResponse;
|
|
16
|
+
/**
|
|
17
|
+
* Create a patch response targeting a previous tool call.
|
|
18
|
+
*
|
|
19
|
+
* @param target - The tool_call_id to patch
|
|
20
|
+
* @param patches - Array of JSON Patch (RFC 6902) operations
|
|
21
|
+
* @returns MCP content response with type "meissa/patch"
|
|
22
|
+
*/
|
|
23
|
+
patch(target: string, patches: PatchOperation[]): PatchResponse;
|
|
24
|
+
/**
|
|
25
|
+
* Convert a Meissa schema + data to a markdown string.
|
|
26
|
+
*
|
|
27
|
+
* @param schema - Array of Meissa UI components
|
|
28
|
+
* @param data - Data object for binding resolution
|
|
29
|
+
* @returns Markdown string representation
|
|
30
|
+
*/
|
|
31
|
+
toMarkdown(schema: Schema, data?: Data): string;
|
|
32
|
+
/**
|
|
33
|
+
* Validate a Meissa schema.
|
|
34
|
+
*
|
|
35
|
+
* @param schema - The schema to validate
|
|
36
|
+
* @returns Validation result with valid flag and any errors
|
|
37
|
+
*/
|
|
38
|
+
validate(schema: unknown): ValidationResult;
|
|
39
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Allowed UI component types */
|
|
2
|
+
export type ComponentType = "heading" | "text" | "btn" | "table" | "chart" | "stat" | "list" | "img" | "divider" | "badge" | "code" | "alert" | "progress" | "toggle" | "input" | "select" | "tabs" | "accordion";
|
|
3
|
+
/** Allowed layout component types */
|
|
4
|
+
export type LayoutType = "row" | "col" | "grid" | "split";
|
|
5
|
+
/** All allowed component/layout types */
|
|
6
|
+
export type AllComponentTypes = ComponentType | LayoutType;
|
|
7
|
+
/** A single schema component */
|
|
8
|
+
export interface SchemaComponent {
|
|
9
|
+
type: AllComponentTypes;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
children?: SchemaComponent[];
|
|
12
|
+
}
|
|
13
|
+
/** Schema is an array of components */
|
|
14
|
+
export type Schema = SchemaComponent[];
|
|
15
|
+
/** Data object used to resolve bindings */
|
|
16
|
+
export type Data = Record<string, unknown>;
|
|
17
|
+
/** Options for render() */
|
|
18
|
+
export interface RenderOptions {
|
|
19
|
+
/** Override auto-generated markdown fallback */
|
|
20
|
+
fallback?: string;
|
|
21
|
+
/** Theme object to include in the content block */
|
|
22
|
+
theme?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/** A Meissa content block */
|
|
25
|
+
export interface MeissaContentBlock {
|
|
26
|
+
type: "meissa";
|
|
27
|
+
schema: Schema;
|
|
28
|
+
data: Data;
|
|
29
|
+
fallback: string;
|
|
30
|
+
theme?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/** A Meissa patch content block */
|
|
33
|
+
export interface MeissaPatchContentBlock {
|
|
34
|
+
type: "meissa/patch";
|
|
35
|
+
target: string;
|
|
36
|
+
patches: PatchOperation[];
|
|
37
|
+
}
|
|
38
|
+
/** JSON Patch (RFC 6902) operation */
|
|
39
|
+
export interface PatchOperation {
|
|
40
|
+
op: "replace" | "add" | "remove";
|
|
41
|
+
path: string;
|
|
42
|
+
value?: unknown;
|
|
43
|
+
}
|
|
44
|
+
/** MCP content response for render() */
|
|
45
|
+
export interface RenderResponse {
|
|
46
|
+
content: [MeissaContentBlock];
|
|
47
|
+
}
|
|
48
|
+
/** MCP content response for patch() */
|
|
49
|
+
export interface PatchResponse {
|
|
50
|
+
content: [MeissaPatchContentBlock];
|
|
51
|
+
}
|
|
52
|
+
/** Validation result */
|
|
53
|
+
export interface ValidationResult {
|
|
54
|
+
valid: boolean;
|
|
55
|
+
errors: string[];
|
|
56
|
+
}
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
23
|
+
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/server/index.ts
|
|
40
|
+
var exports_server = {};
|
|
41
|
+
__export(exports_server, {
|
|
42
|
+
validate: () => validate,
|
|
43
|
+
toMarkdown: () => toMarkdown,
|
|
44
|
+
MeissaServer: () => MeissaServer
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(exports_server);
|
|
47
|
+
|
|
48
|
+
// src/server/markdown.ts
|
|
49
|
+
function resolvePath(data, path) {
|
|
50
|
+
const parts = path.split(".");
|
|
51
|
+
let current = data;
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (current === null || current === undefined)
|
|
54
|
+
return;
|
|
55
|
+
if (typeof current === "object") {
|
|
56
|
+
current = current[part];
|
|
57
|
+
} else {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return current;
|
|
62
|
+
}
|
|
63
|
+
function resolveBindings(text, data) {
|
|
64
|
+
return text.replace(/\{\{([^}]+)\}\}/g, (_, path) => {
|
|
65
|
+
const value = resolvePath(data, path.trim());
|
|
66
|
+
if (value === undefined || value === null)
|
|
67
|
+
return "";
|
|
68
|
+
return String(value);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function componentToMarkdown(component, data) {
|
|
72
|
+
const type = component.type;
|
|
73
|
+
switch (type) {
|
|
74
|
+
case "heading": {
|
|
75
|
+
const level = typeof component.level === "number" ? component.level : 2;
|
|
76
|
+
const text = resolveBindings(String(component.text ?? ""), data);
|
|
77
|
+
return `${"#".repeat(level)} ${text}`;
|
|
78
|
+
}
|
|
79
|
+
case "text": {
|
|
80
|
+
const content = resolveBindings(String(component.content ?? component.text ?? ""), data);
|
|
81
|
+
return content;
|
|
82
|
+
}
|
|
83
|
+
case "table": {
|
|
84
|
+
const columns = component.columns;
|
|
85
|
+
const rows = component.rows;
|
|
86
|
+
const dataKey = component.data;
|
|
87
|
+
const resolvedRows = rows ?? (dataKey ? resolvePath(data, dataKey) : []) ?? [];
|
|
88
|
+
if (!columns || !Array.isArray(columns) || columns.length === 0) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
const headers = columns.map((col) => col.header ?? col.key ?? "");
|
|
92
|
+
const keys = columns.map((col) => col.key ?? col.header ?? "");
|
|
93
|
+
const headerRow = `| ${headers.join(" | ")} |`;
|
|
94
|
+
const separator = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
95
|
+
const dataRows = Array.isArray(resolvedRows) ? resolvedRows.map((row) => {
|
|
96
|
+
const cells = keys.map((key) => {
|
|
97
|
+
const val = row[key];
|
|
98
|
+
return val !== undefined && val !== null ? String(val) : "";
|
|
99
|
+
});
|
|
100
|
+
return `| ${cells.join(" | ")} |`;
|
|
101
|
+
}) : [];
|
|
102
|
+
return [headerRow, separator, ...dataRows].join(`
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
case "stat": {
|
|
106
|
+
const label = resolveBindings(String(component.label ?? ""), data);
|
|
107
|
+
const value = resolveBindings(String(component.value ?? ""), data);
|
|
108
|
+
const trend = component.trend ? ` (${resolveBindings(String(component.trend), data)})` : "";
|
|
109
|
+
return `**${label}**: ${value}${trend}`;
|
|
110
|
+
}
|
|
111
|
+
case "list": {
|
|
112
|
+
const items = component.items;
|
|
113
|
+
const dataKey = component.data;
|
|
114
|
+
const ordered = component.ordered === true;
|
|
115
|
+
const resolvedItems = items ?? (dataKey ? resolvePath(data, dataKey) : []) ?? [];
|
|
116
|
+
if (!Array.isArray(resolvedItems))
|
|
117
|
+
return "";
|
|
118
|
+
return resolvedItems.map((item, i) => {
|
|
119
|
+
const text = typeof item === "string" ? item : item?.text ?? "";
|
|
120
|
+
const resolved = resolveBindings(text, data);
|
|
121
|
+
return ordered ? `${i + 1}. ${resolved}` : `- ${resolved}`;
|
|
122
|
+
}).join(`
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
case "btn": {
|
|
126
|
+
const label = resolveBindings(String(component.label ?? component.text ?? ""), data);
|
|
127
|
+
const action = String(component.action ?? component.tool ?? "");
|
|
128
|
+
return `[${label}](action:${action})`;
|
|
129
|
+
}
|
|
130
|
+
case "chart": {
|
|
131
|
+
const chartType = String(component.chartType ?? component.variant ?? "unknown");
|
|
132
|
+
const title = component.title ? resolveBindings(String(component.title), data) : "";
|
|
133
|
+
const titlePart = title ? ` — ${title}` : "";
|
|
134
|
+
return `(Chart: ${chartType}${titlePart})`;
|
|
135
|
+
}
|
|
136
|
+
case "code": {
|
|
137
|
+
const language = String(component.language ?? component.lang ?? "");
|
|
138
|
+
const content = resolveBindings(String(component.content ?? component.code ?? ""), data);
|
|
139
|
+
return `\`\`\`${language}
|
|
140
|
+
${content}
|
|
141
|
+
\`\`\``;
|
|
142
|
+
}
|
|
143
|
+
case "img": {
|
|
144
|
+
const alt = resolveBindings(String(component.alt ?? ""), data);
|
|
145
|
+
const src = resolveBindings(String(component.src ?? component.url ?? ""), data);
|
|
146
|
+
return ``;
|
|
147
|
+
}
|
|
148
|
+
case "divider": {
|
|
149
|
+
return "---";
|
|
150
|
+
}
|
|
151
|
+
case "alert": {
|
|
152
|
+
const variant = String(component.variant ?? "info");
|
|
153
|
+
const content = resolveBindings(String(component.content ?? component.text ?? ""), data);
|
|
154
|
+
const emojiMap = {
|
|
155
|
+
info: "ℹ️",
|
|
156
|
+
warning: "⚠️",
|
|
157
|
+
error: "❌",
|
|
158
|
+
success: "✅"
|
|
159
|
+
};
|
|
160
|
+
const emoji = emojiMap[variant] ?? emojiMap["info"];
|
|
161
|
+
return `> ${emoji} ${content}`;
|
|
162
|
+
}
|
|
163
|
+
case "badge": {
|
|
164
|
+
const text = resolveBindings(String(component.text ?? component.label ?? ""), data);
|
|
165
|
+
return `\`${text}\``;
|
|
166
|
+
}
|
|
167
|
+
case "progress": {
|
|
168
|
+
const value = component.value ?? 0;
|
|
169
|
+
const label = component.label ? resolveBindings(String(component.label), data) : "";
|
|
170
|
+
return label ? `${label}: ${value}%` : `${value}%`;
|
|
171
|
+
}
|
|
172
|
+
case "toggle":
|
|
173
|
+
case "input":
|
|
174
|
+
case "select": {
|
|
175
|
+
const label = resolveBindings(String(component.label ?? ""), data);
|
|
176
|
+
return label ? `[${label}]` : "";
|
|
177
|
+
}
|
|
178
|
+
case "tabs": {
|
|
179
|
+
const tabItems = component.tabs;
|
|
180
|
+
if (!Array.isArray(tabItems))
|
|
181
|
+
return "";
|
|
182
|
+
return tabItems.map((tab) => {
|
|
183
|
+
const label = resolveBindings(String(tab.label ?? ""), data);
|
|
184
|
+
const content = tab.content ? schemaToMarkdown(tab.content, data) : "";
|
|
185
|
+
return `### ${label}
|
|
186
|
+
|
|
187
|
+
${content}`;
|
|
188
|
+
}).join(`
|
|
189
|
+
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
192
|
+
case "accordion": {
|
|
193
|
+
const items = component.items;
|
|
194
|
+
if (!Array.isArray(items))
|
|
195
|
+
return "";
|
|
196
|
+
return items.map((item) => {
|
|
197
|
+
const title = resolveBindings(String(item.title ?? ""), data);
|
|
198
|
+
const content = item.content ? schemaToMarkdown(item.content, data) : "";
|
|
199
|
+
return `**${title}**
|
|
200
|
+
|
|
201
|
+
${content}`;
|
|
202
|
+
}).join(`
|
|
203
|
+
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
case "row":
|
|
207
|
+
case "col":
|
|
208
|
+
case "grid":
|
|
209
|
+
case "split": {
|
|
210
|
+
const children = component.children;
|
|
211
|
+
if (!Array.isArray(children))
|
|
212
|
+
return "";
|
|
213
|
+
return schemaToMarkdown(children, data);
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function schemaToMarkdown(schema, data) {
|
|
220
|
+
return schema.map((component) => componentToMarkdown(component, data)).filter((line) => line !== "").join(`
|
|
221
|
+
|
|
222
|
+
`);
|
|
223
|
+
}
|
|
224
|
+
function toMarkdown(schema, data = {}) {
|
|
225
|
+
return schemaToMarkdown(schema, data);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/server/validator.ts
|
|
229
|
+
var ALLOWED_COMPONENT_TYPES = new Set([
|
|
230
|
+
"heading",
|
|
231
|
+
"text",
|
|
232
|
+
"btn",
|
|
233
|
+
"table",
|
|
234
|
+
"chart",
|
|
235
|
+
"stat",
|
|
236
|
+
"list",
|
|
237
|
+
"img",
|
|
238
|
+
"divider",
|
|
239
|
+
"badge",
|
|
240
|
+
"code",
|
|
241
|
+
"alert",
|
|
242
|
+
"progress",
|
|
243
|
+
"toggle",
|
|
244
|
+
"input",
|
|
245
|
+
"select",
|
|
246
|
+
"tabs",
|
|
247
|
+
"accordion",
|
|
248
|
+
"row",
|
|
249
|
+
"col",
|
|
250
|
+
"grid",
|
|
251
|
+
"split"
|
|
252
|
+
]);
|
|
253
|
+
var MAX_NESTING_DEPTH = 10;
|
|
254
|
+
var MAX_COMPONENT_COUNT = 500;
|
|
255
|
+
var MAX_SCHEMA_SIZE_BYTES = 64 * 1024;
|
|
256
|
+
function countComponents(schema, depth, errors) {
|
|
257
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
258
|
+
errors.push(`Nesting depth exceeds maximum of ${MAX_NESTING_DEPTH}`);
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
let count = 0;
|
|
262
|
+
for (const component of schema) {
|
|
263
|
+
count++;
|
|
264
|
+
if (!component.type || typeof component.type !== "string") {
|
|
265
|
+
errors.push(`Component missing valid "type" field`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!ALLOWED_COMPONENT_TYPES.has(component.type)) {
|
|
269
|
+
errors.push(`Unknown component type: "${component.type}"`);
|
|
270
|
+
}
|
|
271
|
+
if (component.children && Array.isArray(component.children)) {
|
|
272
|
+
count += countComponents(component.children, depth + 1, errors);
|
|
273
|
+
}
|
|
274
|
+
if (component.type === "tabs" && Array.isArray(component.tabs)) {
|
|
275
|
+
for (const tab of component.tabs) {
|
|
276
|
+
if (tab.content && Array.isArray(tab.content)) {
|
|
277
|
+
count += countComponents(tab.content, depth + 1, errors);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (component.type === "accordion" && Array.isArray(component.items)) {
|
|
282
|
+
for (const item of component.items) {
|
|
283
|
+
if (item.content && Array.isArray(item.content)) {
|
|
284
|
+
count += countComponents(item.content, depth + 1, errors);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return count;
|
|
290
|
+
}
|
|
291
|
+
function validate(schema) {
|
|
292
|
+
const errors = [];
|
|
293
|
+
if (!Array.isArray(schema)) {
|
|
294
|
+
errors.push("Schema must be an array");
|
|
295
|
+
return { valid: false, errors };
|
|
296
|
+
}
|
|
297
|
+
const serialized = JSON.stringify(schema);
|
|
298
|
+
if (serialized.length > MAX_SCHEMA_SIZE_BYTES) {
|
|
299
|
+
errors.push(`Schema size (${serialized.length} bytes) exceeds maximum of ${MAX_SCHEMA_SIZE_BYTES} bytes (64KB)`);
|
|
300
|
+
}
|
|
301
|
+
const totalCount = countComponents(schema, 1, errors);
|
|
302
|
+
if (totalCount > MAX_COMPONENT_COUNT) {
|
|
303
|
+
errors.push(`Total component count (${totalCount}) exceeds maximum of ${MAX_COMPONENT_COUNT}`);
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
valid: errors.length === 0,
|
|
307
|
+
errors
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/server/server.ts
|
|
312
|
+
class MeissaServer {
|
|
313
|
+
render(schema, data = {}, opts) {
|
|
314
|
+
const fallback = opts?.fallback ?? toMarkdown(schema, data);
|
|
315
|
+
const contentBlock = {
|
|
316
|
+
type: "meissa",
|
|
317
|
+
schema,
|
|
318
|
+
data,
|
|
319
|
+
fallback
|
|
320
|
+
};
|
|
321
|
+
if (opts?.theme) {
|
|
322
|
+
contentBlock.theme = opts.theme;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
content: [contentBlock]
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
patch(target, patches) {
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "meissa/patch",
|
|
333
|
+
target,
|
|
334
|
+
patches
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
toMarkdown(schema, data = {}) {
|
|
340
|
+
return toMarkdown(schema, data);
|
|
341
|
+
}
|
|
342
|
+
validate(schema) {
|
|
343
|
+
return validate(schema);
|
|
344
|
+
}
|
|
345
|
+
}
|