mockpit 0.0.0-stage → 1.0.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/LICENSE +22 -0
- package/README.md +232 -2
- package/bin/demoData.js +132 -0
- package/bin/initDesign.js +846 -0
- package/bin/mockpit.js +1830 -0
- package/bin/serveUrl.d.ts +1 -0
- package/bin/serveUrl.js +7 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +230 -0
- package/dist/server/apiViews.d.ts +329 -0
- package/dist/server/apiViews.js +135 -0
- package/dist/server/app.d.ts +53 -0
- package/dist/server/app.js +3112 -0
- package/dist/server/base64.d.ts +1 -0
- package/dist/server/base64.js +16 -0
- package/dist/server/cdn.d.ts +6 -0
- package/dist/server/cdn.js +33 -0
- package/dist/server/codeRunner.d.ts +5 -0
- package/dist/server/codeRunner.js +89 -0
- package/dist/server/codeWorker.d.ts +1 -0
- package/dist/server/codeWorker.js +196 -0
- package/dist/server/designGuide.d.ts +16 -0
- package/dist/server/designGuide.js +311 -0
- package/dist/server/events.d.ts +53 -0
- package/dist/server/events.js +14 -0
- package/dist/server/feedbackBatch.d.ts +43 -0
- package/dist/server/feedbackBatch.js +98 -0
- package/dist/server/icons.d.ts +50 -0
- package/dist/server/icons.js +225 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +64 -0
- package/dist/server/kits.d.ts +36 -0
- package/dist/server/kits.js +279 -0
- package/dist/server/knobs.d.ts +15 -0
- package/dist/server/knobs.js +333 -0
- package/dist/server/mcpHttp.d.ts +53 -0
- package/dist/server/mcpHttp.js +143 -0
- package/dist/server/mcpSpec.d.ts +191 -0
- package/dist/server/mcpSpec.js +402 -0
- package/dist/server/parts.d.ts +31 -0
- package/dist/server/parts.js +338 -0
- package/dist/server/postMarkdown.d.ts +10 -0
- package/dist/server/postMarkdown.js +271 -0
- package/dist/server/postSurfaces.d.ts +40 -0
- package/dist/server/postSurfaces.js +407 -0
- package/dist/server/public.d.ts +5 -0
- package/dist/server/public.js +5 -0
- package/dist/server/push.d.ts +45 -0
- package/dist/server/push.js +234 -0
- package/dist/server/richRender.d.ts +15 -0
- package/dist/server/richRender.js +392 -0
- package/dist/server/run.d.ts +82 -0
- package/dist/server/run.js +290 -0
- package/dist/server/runApi.d.ts +6 -0
- package/dist/server/runApi.js +82 -0
- package/dist/server/slots.d.ts +14 -0
- package/dist/server/slots.js +71 -0
- package/dist/server/sqlStore.d.ts +119 -0
- package/dist/server/sqlStore.js +954 -0
- package/dist/server/sqliteStorage.d.ts +2 -0
- package/dist/server/sqliteStorage.js +90 -0
- package/dist/server/surfacePage.d.ts +48 -0
- package/dist/server/surfacePage.js +1099 -0
- package/dist/server/themes.d.ts +48 -0
- package/dist/server/themes.js +305 -0
- package/dist/server/types.d.ts +599 -0
- package/dist/server/types.js +299 -0
- package/dist/workers/public.d.ts +1 -0
- package/dist/workers/public.js +3 -0
- package/extensions/mockpit.js +703 -0
- package/guide/AGENT_SETUP.md +23 -0
- package/guide/topics/asks.md +87 -0
- package/guide/topics/feedback.md +127 -0
- package/guide/topics/html.md +175 -0
- package/guide/topics/http.md +76 -0
- package/guide/topics/knobs.md +72 -0
- package/guide/topics/scripts.md +129 -0
- package/guide/topics/surfaces.md +100 -0
- package/package.json +165 -4
- package/skills/launch-video/SKILL.md +159 -0
- package/skills/mockpit/SKILL.md +40 -0
- package/viewer/dist/index.html +1922 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isKnownKit, KIT_IDS } from "./kits.js";
|
|
3
|
+
import { isSurfaceKind } from "./types.js";
|
|
4
|
+
const MERMAID_PARSE_NEXT_STEPS = [
|
|
5
|
+
"Correct the Mermaid syntax described by parserMessage, then retry the request.",
|
|
6
|
+
'Keep the diagram declaration on the first non-comment line (for example, "flowchart TD").',
|
|
7
|
+
"Reduce the diagram to a minimal valid form, then add statements back until the failing line is isolated.",
|
|
8
|
+
];
|
|
9
|
+
const requiredString = (name) => z.string({
|
|
10
|
+
required_error: `requires string "${name}"`,
|
|
11
|
+
invalid_type_error: `requires string "${name}"`,
|
|
12
|
+
});
|
|
13
|
+
const optionalLooseString = z.preprocess((v) => (typeof v === "string" ? v : undefined), z.string().optional());
|
|
14
|
+
const looseLayout = z.preprocess((v) => (v === "unified" || v === "split" ? v : undefined), z.enum(["unified", "split"]).optional());
|
|
15
|
+
const optionalLooseNumber = z.preprocess((v) => (typeof v === "number" && Number.isFinite(v) ? v : undefined), z.number().optional());
|
|
16
|
+
const strictDiffFile = z.object({
|
|
17
|
+
filename: requiredString("filename"),
|
|
18
|
+
before: z.string({
|
|
19
|
+
required_error: 'requires string "before" and "after"',
|
|
20
|
+
invalid_type_error: 'requires string "before" and "after"',
|
|
21
|
+
}),
|
|
22
|
+
after: z.string({
|
|
23
|
+
required_error: 'requires string "before" and "after"',
|
|
24
|
+
invalid_type_error: 'requires string "before" and "after"',
|
|
25
|
+
}),
|
|
26
|
+
language: z.string().optional(),
|
|
27
|
+
});
|
|
28
|
+
const looseDiffFile = z
|
|
29
|
+
.object({
|
|
30
|
+
filename: z.string(),
|
|
31
|
+
before: z.unknown().optional(),
|
|
32
|
+
after: z.unknown().optional(),
|
|
33
|
+
language: optionalLooseString,
|
|
34
|
+
})
|
|
35
|
+
.transform(({ filename, before, after, language }) => ({
|
|
36
|
+
filename,
|
|
37
|
+
before: String(before ?? ""),
|
|
38
|
+
after: String(after ?? ""),
|
|
39
|
+
...(language && { language }),
|
|
40
|
+
}));
|
|
41
|
+
const filteredArray = (schema) => z.preprocess((raw) => {
|
|
42
|
+
if (!Array.isArray(raw))
|
|
43
|
+
return raw;
|
|
44
|
+
return raw.flatMap((item) => {
|
|
45
|
+
const parsed = schema.safeParse(item);
|
|
46
|
+
return parsed.success ? [parsed.data] : [];
|
|
47
|
+
});
|
|
48
|
+
}, z.array(schema));
|
|
49
|
+
// `kits` opts an html surface into style/behavior bundles (kits.ts) or the
|
|
50
|
+
// project's own kits. The ids are checked after the parse (checkKits), since
|
|
51
|
+
// which ids exist depends on the project being written to.
|
|
52
|
+
const strictHtmlSurface = z.object({
|
|
53
|
+
kind: z.literal("html"),
|
|
54
|
+
html: requiredString("html"),
|
|
55
|
+
kits: z.array(z.string()).optional(),
|
|
56
|
+
});
|
|
57
|
+
const looseHtmlSurface = z
|
|
58
|
+
.object({
|
|
59
|
+
kind: z.literal("html"),
|
|
60
|
+
html: requiredString("html"),
|
|
61
|
+
kits: z.unknown().optional(),
|
|
62
|
+
})
|
|
63
|
+
.transform((p) => ({
|
|
64
|
+
kind: "html",
|
|
65
|
+
html: p.html,
|
|
66
|
+
...(Array.isArray(p.kits) ? { kits: p.kits.filter((k) => typeof k === "string") } : {}),
|
|
67
|
+
}));
|
|
68
|
+
// Strict mode rejects an unknown id with the valid set, so a CLI/REST typo is a
|
|
69
|
+
// clean 400; loose mode filters unknown ids out rather than dropping the whole
|
|
70
|
+
// surface, and omits the field when none remain so a junk `kits` never lingers
|
|
71
|
+
// as an empty key.
|
|
72
|
+
function checkKits(surface, path, scope) {
|
|
73
|
+
if (surface.kind !== "html" || surface.kits === undefined)
|
|
74
|
+
return { surface, errors: [] };
|
|
75
|
+
const own = scope.projectKits ?? [];
|
|
76
|
+
const known = (id) => isKnownKit(id) || own.includes(id);
|
|
77
|
+
const errors = surface.kits.flatMap((id, i) => known(id)
|
|
78
|
+
? []
|
|
79
|
+
: [
|
|
80
|
+
{
|
|
81
|
+
code: "invalid_surface",
|
|
82
|
+
requestPath: `${path}.kits.${i}`,
|
|
83
|
+
message: `unknown kit "${id}" — known: ${[...KIT_IDS, ...own].join(", ")}`,
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
const kits = surface.kits.filter(known);
|
|
87
|
+
const { kits: _drop, ...rest } = surface;
|
|
88
|
+
return { surface: kits.length > 0 ? { ...rest, kits } : rest, errors };
|
|
89
|
+
}
|
|
90
|
+
const strictMarkdownSurface = z.object({
|
|
91
|
+
kind: z.literal("markdown"),
|
|
92
|
+
markdown: requiredString("markdown"),
|
|
93
|
+
});
|
|
94
|
+
// Loose mode drops a blank markdown surface rather than publishing an empty card.
|
|
95
|
+
const looseMarkdownSurface = z
|
|
96
|
+
.object({ kind: z.literal("markdown"), markdown: z.string() })
|
|
97
|
+
.refine((p) => p.markdown.trim().length > 0, {
|
|
98
|
+
message: 'markdown surface requires non-empty "markdown"',
|
|
99
|
+
});
|
|
100
|
+
const strictMermaidSurface = z.object({
|
|
101
|
+
kind: z.literal("mermaid"),
|
|
102
|
+
mermaid: requiredString("mermaid"),
|
|
103
|
+
});
|
|
104
|
+
// Loose mode drops a blank mermaid surface rather than publishing an empty card.
|
|
105
|
+
const looseMermaidSurface = z
|
|
106
|
+
.object({ kind: z.literal("mermaid"), mermaid: z.string() })
|
|
107
|
+
.refine((p) => p.mermaid.trim().length > 0, {
|
|
108
|
+
message: 'mermaid surface requires non-empty "mermaid"',
|
|
109
|
+
});
|
|
110
|
+
const strictDiffSurface = z
|
|
111
|
+
.object({
|
|
112
|
+
kind: z.literal("diff"),
|
|
113
|
+
patch: z.string().optional(),
|
|
114
|
+
files: z.array(strictDiffFile).optional(),
|
|
115
|
+
layout: z.enum(["unified", "split"]).optional(),
|
|
116
|
+
})
|
|
117
|
+
.refine((p) => !!p.patch || (p.files?.length ?? 0) > 0, {
|
|
118
|
+
message: 'diff surface requires string "patch" or non-empty "files"',
|
|
119
|
+
});
|
|
120
|
+
const looseDiffSurface = z
|
|
121
|
+
.object({
|
|
122
|
+
kind: z.literal("diff"),
|
|
123
|
+
patch: optionalLooseString,
|
|
124
|
+
files: filteredArray(looseDiffFile).optional(),
|
|
125
|
+
layout: looseLayout,
|
|
126
|
+
})
|
|
127
|
+
.refine((p) => !!p.patch || (p.files?.length ?? 0) > 0, {
|
|
128
|
+
message: 'diff surface requires string "patch" or non-empty "files"',
|
|
129
|
+
});
|
|
130
|
+
const strictImageSurface = z.object({
|
|
131
|
+
kind: z.literal("image"),
|
|
132
|
+
assetId: requiredString("assetId"),
|
|
133
|
+
alt: z.string().optional(),
|
|
134
|
+
caption: z.string().optional(),
|
|
135
|
+
});
|
|
136
|
+
const looseImageSurface = z.object({
|
|
137
|
+
kind: z.literal("image"),
|
|
138
|
+
assetId: z.string(),
|
|
139
|
+
alt: optionalLooseString,
|
|
140
|
+
caption: optionalLooseString,
|
|
141
|
+
});
|
|
142
|
+
const strictTerminalSurface = z.object({
|
|
143
|
+
kind: z.literal("terminal"),
|
|
144
|
+
text: requiredString("text"),
|
|
145
|
+
cols: z.number().optional(),
|
|
146
|
+
title: z.string().optional(),
|
|
147
|
+
});
|
|
148
|
+
const looseTerminalSurface = z.object({
|
|
149
|
+
kind: z.literal("terminal"),
|
|
150
|
+
text: z.string(),
|
|
151
|
+
cols: optionalLooseNumber,
|
|
152
|
+
title: optionalLooseString,
|
|
153
|
+
});
|
|
154
|
+
// A json surface carries a pre-parsed JSON value (`data: unknown`). Strict mode
|
|
155
|
+
// rejects a missing `data` key (null is valid — it's a JSON value); loose mode
|
|
156
|
+
// drops the surface if `data` is absent. The transform fixes zod's inference:
|
|
157
|
+
// z.unknown() marks the key optional, but data is always present after the
|
|
158
|
+
// refine, so the output type must be { kind: "json"; data: unknown }.
|
|
159
|
+
const strictJsonSurface = z
|
|
160
|
+
.object({
|
|
161
|
+
kind: z.literal("json"),
|
|
162
|
+
data: z.unknown(),
|
|
163
|
+
})
|
|
164
|
+
.refine((p) => p.data !== undefined, {
|
|
165
|
+
message: 'json surface requires "data"',
|
|
166
|
+
})
|
|
167
|
+
.transform((p) => ({ kind: "json", data: p.data }));
|
|
168
|
+
const looseJsonSurface = z
|
|
169
|
+
.object({
|
|
170
|
+
kind: z.literal("json"),
|
|
171
|
+
data: z.unknown(),
|
|
172
|
+
})
|
|
173
|
+
.refine((p) => p.data !== undefined, {
|
|
174
|
+
message: 'json surface requires "data"',
|
|
175
|
+
})
|
|
176
|
+
.transform((p) => ({ kind: "json", data: p.data }));
|
|
177
|
+
const strictCodeSurface = z.object({
|
|
178
|
+
kind: z.literal("code"),
|
|
179
|
+
code: requiredString("code"),
|
|
180
|
+
language: z.string().optional(),
|
|
181
|
+
title: z.string().optional(),
|
|
182
|
+
lineStart: z.number().int().min(1).optional(),
|
|
183
|
+
});
|
|
184
|
+
const looseCodeSurface = z.object({
|
|
185
|
+
kind: z.literal("code"),
|
|
186
|
+
code: z.string(),
|
|
187
|
+
language: optionalLooseString,
|
|
188
|
+
title: optionalLooseString,
|
|
189
|
+
lineStart: optionalLooseNumber,
|
|
190
|
+
});
|
|
191
|
+
const looseSurfaceSchema = z.union([
|
|
192
|
+
looseHtmlSurface,
|
|
193
|
+
looseMarkdownSurface,
|
|
194
|
+
looseMermaidSurface,
|
|
195
|
+
looseDiffSurface,
|
|
196
|
+
looseImageSurface,
|
|
197
|
+
looseTerminalSurface,
|
|
198
|
+
looseJsonSurface,
|
|
199
|
+
looseCodeSurface,
|
|
200
|
+
]);
|
|
201
|
+
const strictSurfaceSchemas = {
|
|
202
|
+
html: strictHtmlSurface,
|
|
203
|
+
markdown: strictMarkdownSurface,
|
|
204
|
+
mermaid: strictMermaidSurface,
|
|
205
|
+
diff: strictDiffSurface,
|
|
206
|
+
image: strictImageSurface,
|
|
207
|
+
terminal: strictTerminalSurface,
|
|
208
|
+
json: strictJsonSurface,
|
|
209
|
+
code: strictCodeSurface,
|
|
210
|
+
};
|
|
211
|
+
// Runtime surface parser shared by REST and MCP. REST uses strict mode to
|
|
212
|
+
// reject malformed input before it reaches storage; MCP uses tolerant mode so
|
|
213
|
+
// slightly-off tool calls still publish whatever valid surfaces they contain.
|
|
214
|
+
// Async because mermaid validation awaits the parser (@mermaid-js/parser).
|
|
215
|
+
async function parseSurfaceList(raw, opts = {}) {
|
|
216
|
+
if (!Array.isArray(raw)) {
|
|
217
|
+
return surfaceResult([], [{ code: "invalid_surface", requestPath: "surfaces", message: "must be an array" }]);
|
|
218
|
+
}
|
|
219
|
+
if (opts.strict === true) {
|
|
220
|
+
const results = await Promise.all(raw.map((surface, i) => parseStrictSurface(surface, i, opts)));
|
|
221
|
+
return surfaceResult(results.flatMap((r) => (r.surface ? [r.surface] : [])), results.flatMap((r) => r.errors));
|
|
222
|
+
}
|
|
223
|
+
const surfaces = [];
|
|
224
|
+
for (const [index, surface] of raw.entries()) {
|
|
225
|
+
const parsed = looseSurfaceSchema.safeParse(surface);
|
|
226
|
+
if (!parsed.success)
|
|
227
|
+
continue;
|
|
228
|
+
const { surface: kept } = checkKits(parsed.data, `surfaces[${index}]`, opts);
|
|
229
|
+
if ((await validateSemantics(kept, `surfaces[${index}]`)).length === 0)
|
|
230
|
+
surfaces.push(kept);
|
|
231
|
+
}
|
|
232
|
+
return surfaceResult(surfaces, []);
|
|
233
|
+
}
|
|
234
|
+
function surfaceResult(surfaces, errors) {
|
|
235
|
+
return { surfaces, parts: surfaces, errors };
|
|
236
|
+
}
|
|
237
|
+
export const coerceSurfaces = (raw, scope = {}) => parseSurfaceList(raw, scope).then((r) => r.surfaces);
|
|
238
|
+
/** Strictly validates surfaces before a native API write reaches persistence. */
|
|
239
|
+
export async function validateSurfaces(raw, scope = {}) {
|
|
240
|
+
const result = await parseSurfaceList(raw, { ...scope, strict: true });
|
|
241
|
+
return result.errors.length > 0
|
|
242
|
+
? {
|
|
243
|
+
ok: false,
|
|
244
|
+
error: result.errors.map((issue) => `${issue.requestPath}: ${issue.message}`).join("; "),
|
|
245
|
+
code: "surface_validation_failed",
|
|
246
|
+
issues: result.errors,
|
|
247
|
+
}
|
|
248
|
+
: { ok: true, surfaces: result.surfaces, parts: result.surfaces };
|
|
249
|
+
}
|
|
250
|
+
// Renderability checks that run after the structural zod parse succeeds. Strict
|
|
251
|
+
// mode reports these as 400s; loose mode (MCP) drops the surface. Runtime-agnostic:
|
|
252
|
+
// the parsers used here are the same ones richRender.ts runs server-side (JS
|
|
253
|
+
// regex engine, no DOM/WASM), so this is safe on the Worker DO too. The mermaid
|
|
254
|
+
// parser (@mermaid-js/parser, the official extraction) covers the 15
|
|
255
|
+
// Langium-migrated diagram types; types still on Jison (flowchart, sequence,
|
|
256
|
+
// class, state, er, gantt, …) are not yet in that package, so we skip
|
|
257
|
+
// validation for them — the viewer's existing graceful fallback handles any
|
|
258
|
+
// render failure. No `node:` imports, no DOM usage on the parse path.
|
|
259
|
+
//
|
|
260
|
+
// Both parsers are imported lazily, at their point of use in validateSemantics
|
|
261
|
+
// below — see the note there.
|
|
262
|
+
// Takes the parsers rather than importing them, so the caller can load the module
|
|
263
|
+
// OUTSIDE its try/catch — see the mermaid branch below for why that matters.
|
|
264
|
+
function diffPatchHasContent(patch, { processFile, parsePatchFiles }) {
|
|
265
|
+
let files = 0;
|
|
266
|
+
for (const parsed of parsePatchFiles(patch))
|
|
267
|
+
files += parsed.files.length;
|
|
268
|
+
if (files > 0)
|
|
269
|
+
return true;
|
|
270
|
+
const fd = processFile(patch);
|
|
271
|
+
return !!(fd && (fd.name || fd.hunks?.length));
|
|
272
|
+
}
|
|
273
|
+
// Extract the diagram type keyword from mermaid source (the first non-empty,
|
|
274
|
+
// non-comment line's first word). The official parser's `parse(diagramType,
|
|
275
|
+
// text)` takes this as a separate parameter.
|
|
276
|
+
function mermaidDiagramType(src) {
|
|
277
|
+
for (const line of src.split("\n")) {
|
|
278
|
+
const trimmed = line.trim();
|
|
279
|
+
if (!trimmed || trimmed.startsWith("%%") || trimmed.startsWith("#"))
|
|
280
|
+
continue;
|
|
281
|
+
return trimmed.split(/\s+/)[0] ?? null;
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
// The two parsers load at their point of use, not at module load. Together they
|
|
286
|
+
// are ~51 MB of RSS and ~210 ms of import time (`npm run bench:all`, process
|
|
287
|
+
// suite), and they are only reachable when someone publishes a diff or a mermaid
|
|
288
|
+
// surface — a static import made every server pay that at boot to validate content
|
|
289
|
+
// it may never receive. The runtime's module cache makes later publishes cheap,
|
|
290
|
+
// and @pierre/diffs is shared with richRender.ts, so a workspace that renders
|
|
291
|
+
// diffs loads it once for both.
|
|
292
|
+
//
|
|
293
|
+
// This is the other half of app.ts's lazy richRender import: leaving either half
|
|
294
|
+
// static keeps the whole dependency graph resident and neither one saves anything.
|
|
295
|
+
async function validateSemantics(surface, surfaceRequestPath) {
|
|
296
|
+
if (surface.kind === "diff" && surface.patch) {
|
|
297
|
+
const diffParsers = await import("@pierre/diffs");
|
|
298
|
+
try {
|
|
299
|
+
if (!diffPatchHasContent(surface.patch, diffParsers))
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
code: "invalid_surface",
|
|
303
|
+
requestPath: `${surfaceRequestPath}.patch`,
|
|
304
|
+
message: "did not parse to any file — expected a unified/git patch with --- /+++ headers and @@ hunks",
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
return [
|
|
310
|
+
{
|
|
311
|
+
code: "invalid_surface",
|
|
312
|
+
requestPath: `${surfaceRequestPath}.patch`,
|
|
313
|
+
message: "failed to parse: " + (e instanceof Error ? e.message : "error"),
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (surface.kind === "mermaid") {
|
|
319
|
+
const diagramType = mermaidDiagramType(surface.mermaid);
|
|
320
|
+
if (!diagramType) {
|
|
321
|
+
return [
|
|
322
|
+
{
|
|
323
|
+
code: "invalid_mermaid_syntax",
|
|
324
|
+
requestPath: `${surfaceRequestPath}.mermaid`,
|
|
325
|
+
message: 'has no diagram type (first line should be e.g. "flowchart TD")',
|
|
326
|
+
diagramType: null,
|
|
327
|
+
parserMessage: "No Mermaid diagram type was found.",
|
|
328
|
+
nextSteps: MERMAID_PARSE_NEXT_STEPS,
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
// Outside the try: the catch below turns a throw into "your mermaid is
|
|
333
|
+
// invalid", which would be a wrong answer (and a 400 instead of a 500) if what
|
|
334
|
+
// actually failed was loading the parser.
|
|
335
|
+
const { parse: parseMermaid } = await import("@mermaid-js/parser");
|
|
336
|
+
try {
|
|
337
|
+
await parseMermaid(diagramType, surface.mermaid);
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
// Unsupported diagram types (flowchart, sequence, etc. — still on Jison)
|
|
341
|
+
// skip validation; the viewer's graceful fallback handles render failures.
|
|
342
|
+
if (e instanceof Error && /unknown diagram type/i.test(e.message))
|
|
343
|
+
return [];
|
|
344
|
+
const parserMessage = e instanceof Error ? e.message : "parse error";
|
|
345
|
+
const summary = parserMessage.split("\n")[0] ?? "parse error";
|
|
346
|
+
return [
|
|
347
|
+
{
|
|
348
|
+
code: "invalid_mermaid_syntax",
|
|
349
|
+
requestPath: `${surfaceRequestPath}.mermaid`,
|
|
350
|
+
message: `failed to parse: ${summary}`,
|
|
351
|
+
diagramType,
|
|
352
|
+
parserMessage,
|
|
353
|
+
nextSteps: MERMAID_PARSE_NEXT_STEPS,
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
async function parseStrictSurface(raw, index, scope) {
|
|
361
|
+
const surfaceRequestPath = `surfaces[${index}]`;
|
|
362
|
+
if (!raw || typeof raw !== "object")
|
|
363
|
+
return {
|
|
364
|
+
surface: null,
|
|
365
|
+
errors: [
|
|
366
|
+
{ code: "invalid_surface", requestPath: surfaceRequestPath, message: "must be an object" },
|
|
367
|
+
],
|
|
368
|
+
};
|
|
369
|
+
const kind = raw.kind;
|
|
370
|
+
const schema = schemaForKind(kind);
|
|
371
|
+
if (!schema) {
|
|
372
|
+
return {
|
|
373
|
+
surface: null,
|
|
374
|
+
errors: [
|
|
375
|
+
{
|
|
376
|
+
code: "invalid_surface",
|
|
377
|
+
requestPath: surfaceRequestPath,
|
|
378
|
+
message: "unknown surface kind",
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const parsed = schema.safeParse(raw);
|
|
384
|
+
if (!parsed.success) {
|
|
385
|
+
return { surface: null, errors: formatZodErrors(parsed.error, surfaceRequestPath) };
|
|
386
|
+
}
|
|
387
|
+
const kits = checkKits(parsed.data, surfaceRequestPath, scope);
|
|
388
|
+
if (kits.errors.length > 0)
|
|
389
|
+
return { surface: null, errors: kits.errors };
|
|
390
|
+
const semantic = await validateSemantics(kits.surface, surfaceRequestPath);
|
|
391
|
+
return semantic.length > 0
|
|
392
|
+
? { surface: null, errors: semantic }
|
|
393
|
+
: { surface: kits.surface, errors: [] };
|
|
394
|
+
}
|
|
395
|
+
function schemaForKind(kind) {
|
|
396
|
+
return isSurfaceKind(kind) ? strictSurfaceSchemas[kind] : null;
|
|
397
|
+
}
|
|
398
|
+
function formatZodErrors(error, requestPathPrefix = "surfaces") {
|
|
399
|
+
return error.issues.map((issue) => {
|
|
400
|
+
const suffix = issue.path.length > 0 ? `.${issue.path.join(".")}` : "";
|
|
401
|
+
return {
|
|
402
|
+
code: "invalid_surface",
|
|
403
|
+
requestPath: `${requestPathPrefix}${suffix}`,
|
|
404
|
+
message: issue.message,
|
|
405
|
+
};
|
|
406
|
+
});
|
|
407
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createApp, type AppOptions, type AuthenticateHook, type FeedEvent } from "./app.js";
|
|
2
|
+
export { SqlStore } from "./sqlStore.js";
|
|
3
|
+
export { createSqliteStorage } from "./sqliteStorage.js";
|
|
4
|
+
export type { SurfaceValidationFailure, SurfaceValidationIssue, SurfaceValidationResult, } from "./postSurfaces.js";
|
|
5
|
+
export type * from "./types.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Stable public server-core entrypoint for integrations that reuse mockpit's
|
|
2
|
+
// HTTP/SSE/MCP app without depending on the package's internal dist layout.
|
|
3
|
+
export { createApp } from "./app.js";
|
|
4
|
+
export { SqlStore } from "./sqlStore.js";
|
|
5
|
+
export { createSqliteStorage } from "./sqliteStorage.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Store } from "./types.ts";
|
|
2
|
+
export interface PushSubscription {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
keys: {
|
|
5
|
+
p256dh: string;
|
|
6
|
+
auth: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface Hook {
|
|
10
|
+
id: string;
|
|
11
|
+
url: string;
|
|
12
|
+
events: HookEvent[];
|
|
13
|
+
}
|
|
14
|
+
export type HookEvent = "ask" | "publish" | "decision";
|
|
15
|
+
export interface NotifyPayload {
|
|
16
|
+
event: HookEvent;
|
|
17
|
+
project: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
variant: string;
|
|
20
|
+
version: number;
|
|
21
|
+
text: string;
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
interface Jwk {
|
|
25
|
+
kty?: string;
|
|
26
|
+
crv?: string;
|
|
27
|
+
x?: string;
|
|
28
|
+
y?: string;
|
|
29
|
+
d?: string;
|
|
30
|
+
ext?: boolean;
|
|
31
|
+
key_ops?: string[];
|
|
32
|
+
}
|
|
33
|
+
interface VapidKeys {
|
|
34
|
+
publicKey: string;
|
|
35
|
+
privateJwk: Jwk;
|
|
36
|
+
}
|
|
37
|
+
export declare function vapidKeys(store: Store): Promise<VapidKeys>;
|
|
38
|
+
export declare function listSubscriptions(store: Store): Promise<PushSubscription[]>;
|
|
39
|
+
export declare function isPushSubscription(value: unknown): value is PushSubscription;
|
|
40
|
+
export declare function addSubscription(store: Store, sub: PushSubscription): Promise<void>;
|
|
41
|
+
export declare function listHooks(store: Store): Promise<Hook[]>;
|
|
42
|
+
export declare function addHook(store: Store, hook: Hook): Promise<Hook>;
|
|
43
|
+
export declare function removeHook(store: Store, id: string): Promise<boolean>;
|
|
44
|
+
export declare function notify(store: Store, payload: NotifyPayload): Promise<void>;
|
|
45
|
+
export {};
|