@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
package/dist/server.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// src/server/markdown.ts
|
|
2
|
+
function resolvePath(data, path) {
|
|
3
|
+
const parts = path.split(".");
|
|
4
|
+
let current = data;
|
|
5
|
+
for (const part of parts) {
|
|
6
|
+
if (current === null || current === undefined)
|
|
7
|
+
return;
|
|
8
|
+
if (typeof current === "object") {
|
|
9
|
+
current = current[part];
|
|
10
|
+
} else {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
function resolveBindings(text, data) {
|
|
17
|
+
return text.replace(/\{\{([^}]+)\}\}/g, (_, path) => {
|
|
18
|
+
const value = resolvePath(data, path.trim());
|
|
19
|
+
if (value === undefined || value === null)
|
|
20
|
+
return "";
|
|
21
|
+
return String(value);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function componentToMarkdown(component, data) {
|
|
25
|
+
const type = component.type;
|
|
26
|
+
switch (type) {
|
|
27
|
+
case "heading": {
|
|
28
|
+
const level = typeof component.level === "number" ? component.level : 2;
|
|
29
|
+
const text = resolveBindings(String(component.text ?? ""), data);
|
|
30
|
+
return `${"#".repeat(level)} ${text}`;
|
|
31
|
+
}
|
|
32
|
+
case "text": {
|
|
33
|
+
const content = resolveBindings(String(component.content ?? component.text ?? ""), data);
|
|
34
|
+
return content;
|
|
35
|
+
}
|
|
36
|
+
case "table": {
|
|
37
|
+
const columns = component.columns;
|
|
38
|
+
const rows = component.rows;
|
|
39
|
+
const dataKey = component.data;
|
|
40
|
+
const resolvedRows = rows ?? (dataKey ? resolvePath(data, dataKey) : []) ?? [];
|
|
41
|
+
if (!columns || !Array.isArray(columns) || columns.length === 0) {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
const headers = columns.map((col) => col.header ?? col.key ?? "");
|
|
45
|
+
const keys = columns.map((col) => col.key ?? col.header ?? "");
|
|
46
|
+
const headerRow = `| ${headers.join(" | ")} |`;
|
|
47
|
+
const separator = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
48
|
+
const dataRows = Array.isArray(resolvedRows) ? resolvedRows.map((row) => {
|
|
49
|
+
const cells = keys.map((key) => {
|
|
50
|
+
const val = row[key];
|
|
51
|
+
return val !== undefined && val !== null ? String(val) : "";
|
|
52
|
+
});
|
|
53
|
+
return `| ${cells.join(" | ")} |`;
|
|
54
|
+
}) : [];
|
|
55
|
+
return [headerRow, separator, ...dataRows].join(`
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
case "stat": {
|
|
59
|
+
const label = resolveBindings(String(component.label ?? ""), data);
|
|
60
|
+
const value = resolveBindings(String(component.value ?? ""), data);
|
|
61
|
+
const trend = component.trend ? ` (${resolveBindings(String(component.trend), data)})` : "";
|
|
62
|
+
return `**${label}**: ${value}${trend}`;
|
|
63
|
+
}
|
|
64
|
+
case "list": {
|
|
65
|
+
const items = component.items;
|
|
66
|
+
const dataKey = component.data;
|
|
67
|
+
const ordered = component.ordered === true;
|
|
68
|
+
const resolvedItems = items ?? (dataKey ? resolvePath(data, dataKey) : []) ?? [];
|
|
69
|
+
if (!Array.isArray(resolvedItems))
|
|
70
|
+
return "";
|
|
71
|
+
return resolvedItems.map((item, i) => {
|
|
72
|
+
const text = typeof item === "string" ? item : item?.text ?? "";
|
|
73
|
+
const resolved = resolveBindings(text, data);
|
|
74
|
+
return ordered ? `${i + 1}. ${resolved}` : `- ${resolved}`;
|
|
75
|
+
}).join(`
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
case "btn": {
|
|
79
|
+
const label = resolveBindings(String(component.label ?? component.text ?? ""), data);
|
|
80
|
+
const action = String(component.action ?? component.tool ?? "");
|
|
81
|
+
return `[${label}](action:${action})`;
|
|
82
|
+
}
|
|
83
|
+
case "chart": {
|
|
84
|
+
const chartType = String(component.chartType ?? component.variant ?? "unknown");
|
|
85
|
+
const title = component.title ? resolveBindings(String(component.title), data) : "";
|
|
86
|
+
const titlePart = title ? ` — ${title}` : "";
|
|
87
|
+
return `(Chart: ${chartType}${titlePart})`;
|
|
88
|
+
}
|
|
89
|
+
case "code": {
|
|
90
|
+
const language = String(component.language ?? component.lang ?? "");
|
|
91
|
+
const content = resolveBindings(String(component.content ?? component.code ?? ""), data);
|
|
92
|
+
return `\`\`\`${language}
|
|
93
|
+
${content}
|
|
94
|
+
\`\`\``;
|
|
95
|
+
}
|
|
96
|
+
case "img": {
|
|
97
|
+
const alt = resolveBindings(String(component.alt ?? ""), data);
|
|
98
|
+
const src = resolveBindings(String(component.src ?? component.url ?? ""), data);
|
|
99
|
+
return ``;
|
|
100
|
+
}
|
|
101
|
+
case "divider": {
|
|
102
|
+
return "---";
|
|
103
|
+
}
|
|
104
|
+
case "alert": {
|
|
105
|
+
const variant = String(component.variant ?? "info");
|
|
106
|
+
const content = resolveBindings(String(component.content ?? component.text ?? ""), data);
|
|
107
|
+
const emojiMap = {
|
|
108
|
+
info: "ℹ️",
|
|
109
|
+
warning: "⚠️",
|
|
110
|
+
error: "❌",
|
|
111
|
+
success: "✅"
|
|
112
|
+
};
|
|
113
|
+
const emoji = emojiMap[variant] ?? emojiMap["info"];
|
|
114
|
+
return `> ${emoji} ${content}`;
|
|
115
|
+
}
|
|
116
|
+
case "badge": {
|
|
117
|
+
const text = resolveBindings(String(component.text ?? component.label ?? ""), data);
|
|
118
|
+
return `\`${text}\``;
|
|
119
|
+
}
|
|
120
|
+
case "progress": {
|
|
121
|
+
const value = component.value ?? 0;
|
|
122
|
+
const label = component.label ? resolveBindings(String(component.label), data) : "";
|
|
123
|
+
return label ? `${label}: ${value}%` : `${value}%`;
|
|
124
|
+
}
|
|
125
|
+
case "toggle":
|
|
126
|
+
case "input":
|
|
127
|
+
case "select": {
|
|
128
|
+
const label = resolveBindings(String(component.label ?? ""), data);
|
|
129
|
+
return label ? `[${label}]` : "";
|
|
130
|
+
}
|
|
131
|
+
case "tabs": {
|
|
132
|
+
const tabItems = component.tabs;
|
|
133
|
+
if (!Array.isArray(tabItems))
|
|
134
|
+
return "";
|
|
135
|
+
return tabItems.map((tab) => {
|
|
136
|
+
const label = resolveBindings(String(tab.label ?? ""), data);
|
|
137
|
+
const content = tab.content ? schemaToMarkdown(tab.content, data) : "";
|
|
138
|
+
return `### ${label}
|
|
139
|
+
|
|
140
|
+
${content}`;
|
|
141
|
+
}).join(`
|
|
142
|
+
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
case "accordion": {
|
|
146
|
+
const items = component.items;
|
|
147
|
+
if (!Array.isArray(items))
|
|
148
|
+
return "";
|
|
149
|
+
return items.map((item) => {
|
|
150
|
+
const title = resolveBindings(String(item.title ?? ""), data);
|
|
151
|
+
const content = item.content ? schemaToMarkdown(item.content, data) : "";
|
|
152
|
+
return `**${title}**
|
|
153
|
+
|
|
154
|
+
${content}`;
|
|
155
|
+
}).join(`
|
|
156
|
+
|
|
157
|
+
`);
|
|
158
|
+
}
|
|
159
|
+
case "row":
|
|
160
|
+
case "col":
|
|
161
|
+
case "grid":
|
|
162
|
+
case "split": {
|
|
163
|
+
const children = component.children;
|
|
164
|
+
if (!Array.isArray(children))
|
|
165
|
+
return "";
|
|
166
|
+
return schemaToMarkdown(children, data);
|
|
167
|
+
}
|
|
168
|
+
default:
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function schemaToMarkdown(schema, data) {
|
|
173
|
+
return schema.map((component) => componentToMarkdown(component, data)).filter((line) => line !== "").join(`
|
|
174
|
+
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
function toMarkdown(schema, data = {}) {
|
|
178
|
+
return schemaToMarkdown(schema, data);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/server/validator.ts
|
|
182
|
+
var ALLOWED_COMPONENT_TYPES = new Set([
|
|
183
|
+
"heading",
|
|
184
|
+
"text",
|
|
185
|
+
"btn",
|
|
186
|
+
"table",
|
|
187
|
+
"chart",
|
|
188
|
+
"stat",
|
|
189
|
+
"list",
|
|
190
|
+
"img",
|
|
191
|
+
"divider",
|
|
192
|
+
"badge",
|
|
193
|
+
"code",
|
|
194
|
+
"alert",
|
|
195
|
+
"progress",
|
|
196
|
+
"toggle",
|
|
197
|
+
"input",
|
|
198
|
+
"select",
|
|
199
|
+
"tabs",
|
|
200
|
+
"accordion",
|
|
201
|
+
"row",
|
|
202
|
+
"col",
|
|
203
|
+
"grid",
|
|
204
|
+
"split"
|
|
205
|
+
]);
|
|
206
|
+
var MAX_NESTING_DEPTH = 10;
|
|
207
|
+
var MAX_COMPONENT_COUNT = 500;
|
|
208
|
+
var MAX_SCHEMA_SIZE_BYTES = 64 * 1024;
|
|
209
|
+
function countComponents(schema, depth, errors) {
|
|
210
|
+
if (depth > MAX_NESTING_DEPTH) {
|
|
211
|
+
errors.push(`Nesting depth exceeds maximum of ${MAX_NESTING_DEPTH}`);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
let count = 0;
|
|
215
|
+
for (const component of schema) {
|
|
216
|
+
count++;
|
|
217
|
+
if (!component.type || typeof component.type !== "string") {
|
|
218
|
+
errors.push(`Component missing valid "type" field`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!ALLOWED_COMPONENT_TYPES.has(component.type)) {
|
|
222
|
+
errors.push(`Unknown component type: "${component.type}"`);
|
|
223
|
+
}
|
|
224
|
+
if (component.children && Array.isArray(component.children)) {
|
|
225
|
+
count += countComponents(component.children, depth + 1, errors);
|
|
226
|
+
}
|
|
227
|
+
if (component.type === "tabs" && Array.isArray(component.tabs)) {
|
|
228
|
+
for (const tab of component.tabs) {
|
|
229
|
+
if (tab.content && Array.isArray(tab.content)) {
|
|
230
|
+
count += countComponents(tab.content, depth + 1, errors);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (component.type === "accordion" && Array.isArray(component.items)) {
|
|
235
|
+
for (const item of component.items) {
|
|
236
|
+
if (item.content && Array.isArray(item.content)) {
|
|
237
|
+
count += countComponents(item.content, depth + 1, errors);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return count;
|
|
243
|
+
}
|
|
244
|
+
function validate(schema) {
|
|
245
|
+
const errors = [];
|
|
246
|
+
if (!Array.isArray(schema)) {
|
|
247
|
+
errors.push("Schema must be an array");
|
|
248
|
+
return { valid: false, errors };
|
|
249
|
+
}
|
|
250
|
+
const serialized = JSON.stringify(schema);
|
|
251
|
+
if (serialized.length > MAX_SCHEMA_SIZE_BYTES) {
|
|
252
|
+
errors.push(`Schema size (${serialized.length} bytes) exceeds maximum of ${MAX_SCHEMA_SIZE_BYTES} bytes (64KB)`);
|
|
253
|
+
}
|
|
254
|
+
const totalCount = countComponents(schema, 1, errors);
|
|
255
|
+
if (totalCount > MAX_COMPONENT_COUNT) {
|
|
256
|
+
errors.push(`Total component count (${totalCount}) exceeds maximum of ${MAX_COMPONENT_COUNT}`);
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
valid: errors.length === 0,
|
|
260
|
+
errors
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/server/server.ts
|
|
265
|
+
class MeissaServer {
|
|
266
|
+
render(schema, data = {}, opts) {
|
|
267
|
+
const fallback = opts?.fallback ?? toMarkdown(schema, data);
|
|
268
|
+
const contentBlock = {
|
|
269
|
+
type: "meissa",
|
|
270
|
+
schema,
|
|
271
|
+
data,
|
|
272
|
+
fallback
|
|
273
|
+
};
|
|
274
|
+
if (opts?.theme) {
|
|
275
|
+
contentBlock.theme = opts.theme;
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
content: [contentBlock]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
patch(target, patches) {
|
|
282
|
+
return {
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: "meissa/patch",
|
|
286
|
+
target,
|
|
287
|
+
patches
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
toMarkdown(schema, data = {}) {
|
|
293
|
+
return toMarkdown(schema, data);
|
|
294
|
+
}
|
|
295
|
+
validate(schema) {
|
|
296
|
+
return validate(schema);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
export {
|
|
300
|
+
validate,
|
|
301
|
+
toMarkdown,
|
|
302
|
+
MeissaServer
|
|
303
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meissa_a/meissa",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Meissa — token-efficient generative UI for MCP: React renderer, client SDK, server SDK, and sandboxed-iframe apps adapter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/meissa-ai/meissa"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"generative-ui",
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"ai",
|
|
16
|
+
"llm",
|
|
17
|
+
"react",
|
|
18
|
+
"ui-schema",
|
|
19
|
+
"declarative-ui"
|
|
20
|
+
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/renderer/index.d.ts",
|
|
24
|
+
"import": "./dist/renderer.js",
|
|
25
|
+
"require": "./dist/renderer.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./renderer": {
|
|
28
|
+
"types": "./dist/renderer/index.d.ts",
|
|
29
|
+
"import": "./dist/renderer.js",
|
|
30
|
+
"require": "./dist/renderer.cjs"
|
|
31
|
+
},
|
|
32
|
+
"./server": {
|
|
33
|
+
"types": "./dist/server/index.d.ts",
|
|
34
|
+
"import": "./dist/server.js",
|
|
35
|
+
"require": "./dist/server.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./client": {
|
|
38
|
+
"types": "./dist/client/index.d.ts",
|
|
39
|
+
"import": "./dist/client.js",
|
|
40
|
+
"require": "./dist/client.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./apps": {
|
|
43
|
+
"types": "./dist/apps/index.d.ts",
|
|
44
|
+
"import": "./dist/apps.js",
|
|
45
|
+
"require": "./dist/apps.cjs"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "bun run build.ts",
|
|
53
|
+
"dev": "bun run build.ts --watch",
|
|
54
|
+
"test": "bun test",
|
|
55
|
+
"typecheck": "tsc --noEmit"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": "^19.0.0",
|
|
59
|
+
"react-dom": "^19.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"react": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"react-dom": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"clsx": "^2.1.1",
|
|
71
|
+
"tailwind-merge": "^2.6.0",
|
|
72
|
+
"recharts": "^2.15.0"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@types/react": "^19.0.0",
|
|
76
|
+
"@types/react-dom": "^19.0.0",
|
|
77
|
+
"react": "^19.0.0",
|
|
78
|
+
"react-dom": "^19.0.0",
|
|
79
|
+
"typescript": "^5.7.0",
|
|
80
|
+
"@testing-library/react": "^16.1.0",
|
|
81
|
+
"@happy-dom/global-registrator": "^15.0.0",
|
|
82
|
+
"tailwindcss": "^4.0.0"
|
|
83
|
+
}
|
|
84
|
+
}
|