@rse/specbook 0.9.5 → 0.9.7
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 +108 -328
- package/dst/specbook-api.d.ts +13 -20
- package/dst/specbook-api.js +43 -39
- package/dst/specbook-cli.js +42 -55
- package/dst/specbook-cmd-describe.d.ts +5 -2
- package/dst/specbook-cmd-describe.js +30 -91
- package/dst/specbook-cmd-export.d.ts +3 -3
- package/dst/specbook-cmd-export.js +23 -19
- package/dst/specbook-cmd-init.d.ts +3 -3
- package/dst/specbook-cmd-init.js +38 -15
- package/dst/specbook-cmd-lint.d.ts +5 -6
- package/dst/specbook-cmd-lint.js +61 -24
- package/dst/specbook-config.d.ts +2 -2
- package/dst/specbook-config.js +82 -73
- package/dst/specbook-diagnostic.js +4 -1
- package/dst/specbook-diagram.d.ts +6 -5
- package/dst/specbook-diagram.js +94 -104
- package/dst/specbook-export-ast.d.ts +3 -3
- package/dst/specbook-export-ast.js +1 -1
- package/dst/specbook-export-common.d.ts +11 -9
- package/dst/specbook-export-common.js +34 -22
- package/dst/specbook-export-html-search.js +10 -14
- package/dst/specbook-export-html.css +37 -32
- package/dst/specbook-export-html.d.ts +4 -4
- package/dst/specbook-export-html.js +153 -107
- package/dst/specbook-export-logo-dark.svg +35 -0
- package/dst/specbook-export-logo-light.svg +35 -0
- package/dst/specbook-export-md.d.ts +3 -3
- package/dst/specbook-export-md.js +50 -24
- package/dst/specbook-export-pdf.d.ts +4 -2
- package/dst/specbook-export-pdf.js +48 -38
- package/dst/{specbook-struct-schema.d.ts → specbook-format-schema.d.ts} +3 -2
- package/dst/{specbook-struct-schema.js → specbook-format-schema.js} +8 -4
- package/dst/specbook-format-spec.d.ts +31 -0
- package/dst/{specbook-struct-spec.js → specbook-format-spec.js} +10 -10
- package/dst/specbook-format.md +750 -0
- package/dst/specbook-link.d.ts +3 -2
- package/dst/specbook-link.js +7 -4
- package/dst/specbook-mcp.d.ts +2 -1
- package/dst/specbook-mcp.js +30 -62
- package/dst/specbook-parse-common.d.ts +5 -3
- package/dst/specbook-parse-common.js +8 -0
- package/dst/specbook-parse-semantic.d.ts +6 -4
- package/dst/specbook-parse-semantic.js +98 -106
- package/dst/specbook-parse-syntax.d.ts +2 -2
- package/dst/specbook-parse-syntax.js +175 -144
- package/dst/specbook-parse-value.d.ts +1 -1
- package/dst/specbook-parse-value.js +6 -3
- package/dst/specbook-parse.d.ts +4 -7
- package/dst/specbook-parse.js +28 -35
- package/dst/specbook-verbose.d.ts +2 -0
- package/dst/specbook-verbose.js +17 -0
- package/package.json +3 -7
- package/dst/specbook-cmd-edit.d.ts +0 -9
- package/dst/specbook-cmd-edit.js +0 -74
- package/dst/specbook-cmd-import.d.ts +0 -9
- package/dst/specbook-cmd-import.js +0 -78
- package/dst/specbook-export-logo.svg +0 -35
- package/dst/specbook-llm.d.ts +0 -13
- package/dst/specbook-llm.js +0 -51
- package/dst/specbook-struct-spec.d.ts +0 -31
|
@@ -10,10 +10,14 @@ const formatTimestamp = (date) => {
|
|
|
10
10
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
|
11
11
|
`${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
12
12
|
};
|
|
13
|
-
/* render the properties as a value-aligned key/value list
|
|
14
|
-
|
|
13
|
+
/* render the properties as a value-aligned key/value list, wrapping a
|
|
14
|
+
value carrying ";" onto a continuation line, as only a ";"-free first
|
|
15
|
+
line lets the item be recognized as a property again on re-parsing */
|
|
16
|
+
const renderKeyValuesMd = (properties, indent = "") => {
|
|
15
17
|
const width = Math.max(...properties.map((property) => property.key.length)) + 1;
|
|
16
|
-
return properties.map((property) =>
|
|
18
|
+
return properties.map((property) => property.value.includes(";") ?
|
|
19
|
+
`${indent}- ${property.key}:\n${indent} ${property.value}` :
|
|
20
|
+
`${indent}- ${`${property.key}:`.padEnd(width)} ${property.value}`.trimEnd()).join("\n");
|
|
17
21
|
};
|
|
18
22
|
/* render a description statement with its optional rationale */
|
|
19
23
|
const renderDescriptionMd = (description) => description.description + (description.rationale !== undefined ?
|
|
@@ -23,28 +27,42 @@ const renderDescriptionMd = (description) => description.description + (descript
|
|
|
23
27
|
const nameSuffixMd = (object) => (object.primary === true ? " (*)" : "") +
|
|
24
28
|
(object.paren !== undefined ? ` (${object.paren})` : "") +
|
|
25
29
|
(object.anchor !== undefined ? ` {{${object.anchor}}}` : "");
|
|
26
|
-
/*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
/* whether any descendant of an object carries a multi-line description
|
|
31
|
+
(a fenced code block, an ordered list, a blockquote, or multiple
|
|
32
|
+
paragraphs), which no Concise Format item can carry */
|
|
33
|
+
const multiLineBelowMd = (object) => object.childs.some((child) => (child.description !== undefined
|
|
34
|
+
&& renderDescriptionMd(child.description).includes("\n")) || multiLineBelowMd(child));
|
|
35
|
+
/* render an object in the Concise Format as a list item of the
|
|
36
|
+
given nesting depth, with its childs as nested list items */
|
|
37
|
+
const renderConciseMd = (object, depth = 0) => {
|
|
38
|
+
const indent = " ".repeat(depth * 4);
|
|
30
39
|
const segments = [`${object.kind}: ${object.name}${nameSuffixMd(object)}`];
|
|
31
|
-
|
|
40
|
+
/* a value carrying ";" cannot be a segment, so its property has to
|
|
41
|
+
become a nested key/value item below the item instead */
|
|
42
|
+
const wrapped = object.properties.filter((property) => property.value.includes(";"));
|
|
43
|
+
const inline = object.properties.filter((property) => !property.value.includes(";"));
|
|
44
|
+
segments.push(...inline.map((property) => `${property.key}: ${property.value}`));
|
|
32
45
|
if (object.description !== undefined)
|
|
33
46
|
segments.push(renderDescriptionMd(object.description));
|
|
34
|
-
/* a description terminates the item with ".",
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
/* a description terminates the item with ".", which on re-parsing
|
|
48
|
+
marks the last segment as the description (even one shaped like
|
|
49
|
+
"<key>: <value>"), while without one the ";" segment terminator
|
|
50
|
+
has to remain, as only it lets the item be recognized as concise */
|
|
37
51
|
const item = object.description !== undefined ?
|
|
38
52
|
`${indent}- ${segments.join("; ").replace(/\.?$/, ".")}` :
|
|
39
53
|
`${indent}- ${segments.join("; ")};`;
|
|
40
|
-
return [item,
|
|
54
|
+
return [item, renderKeyValuesMd(wrapped, `${indent} `),
|
|
55
|
+
...object.childs.map((child) => renderConciseMd(child, depth + 1))]
|
|
56
|
+
.filter((part) => part !== "").join("\n");
|
|
41
57
|
};
|
|
42
|
-
/* render an object in the Complex Format (levels 1-3),
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
/* render an object in the Complex Format (levels 1-3), with childs from
|
|
59
|
+
level 4 upwards in the Concise Format -- unless a descendant carries
|
|
60
|
+
a multi-line description, so all childs (as siblings have to share
|
|
61
|
+
the format) stay in the Complex Format down to heading level 6 --,
|
|
62
|
+
and the optionally derived Gradia diagram spec embedded as a
|
|
63
|
+
"gradia" fenced code block below the heading */
|
|
46
64
|
const renderObjectMd = (object, level, diagrams) => {
|
|
47
|
-
const heading = `${"#".repeat(level)}${" ".repeat(4 - level)}` +
|
|
65
|
+
const heading = `${"#".repeat(level)}${" ".repeat(Math.max(1, 4 - level))}` +
|
|
48
66
|
`${object.kind}: ${object.name}${nameSuffixMd(object)}`;
|
|
49
67
|
const parts = [heading];
|
|
50
68
|
const spec = diagrams?.get(object);
|
|
@@ -54,14 +72,16 @@ const renderObjectMd = (object, level, diagrams) => {
|
|
|
54
72
|
parts.push(renderKeyValuesMd(object.properties));
|
|
55
73
|
if (object.description !== undefined)
|
|
56
74
|
parts.push(renderDescriptionMd(object.description));
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
/* the childs share one format, as a concise item following
|
|
76
|
+
a heading would attach to the heading's object instead */
|
|
77
|
+
if (level >= 3 && !multiLineBelowMd(object))
|
|
78
|
+
parts.push(object.childs.map((child) => renderConciseMd(child)).join("\n"));
|
|
59
79
|
else
|
|
60
80
|
parts.push(...object.childs.map((child) => renderObjectMd(child, level + 1, diagrams)));
|
|
61
81
|
return parts.filter((part) => part !== "").join("\n\n");
|
|
62
82
|
};
|
|
63
83
|
/* render the entire specification into normalized Markdown */
|
|
64
|
-
export const renderMarkdown =
|
|
84
|
+
export const renderMarkdown = (specification, config) => {
|
|
65
85
|
/* derive the Gradia specs of the diagram-configured objects
|
|
66
86
|
(an invalid diagram situation omits the diagram, as it is
|
|
67
87
|
already reported as a lint diagnostic) */
|
|
@@ -70,9 +90,15 @@ export const renderMarkdown = async (specification, config) => {
|
|
|
70
90
|
for (const [object, result] of specDiagrams(specification, config))
|
|
71
91
|
if (result.spec !== undefined)
|
|
72
92
|
diagrams.set(object, result.spec);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
/* the single frontmatter block (only recognized at the start of a
|
|
94
|
+
file on re-parse) carries the earliest creation and the latest
|
|
95
|
+
modification timestamp of all artifacts */
|
|
96
|
+
const created = new Date(Math.min(...specification.artifacts.map((artifact) => artifact.created.getTime())));
|
|
97
|
+
const modified = new Date(Math.max(...specification.artifacts.map((artifact) => artifact.modified.getTime())));
|
|
98
|
+
return "---\n" +
|
|
99
|
+
`Created: ${formatTimestamp(created)}\n` +
|
|
100
|
+
`Modified: ${formatTimestamp(modified)}\n` +
|
|
76
101
|
"---\n\n" +
|
|
77
|
-
|
|
102
|
+
specification.artifacts.flatMap((artifact) => artifact.objects)
|
|
103
|
+
.map((object) => renderObjectMd(object, 1, diagrams)).join("\n\n") + "\n";
|
|
78
104
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { OutlineEntry } from "./specbook-export-html.js";
|
|
2
2
|
import type { ThemeMapping } from "./specbook-theme.js";
|
|
3
|
-
|
|
3
|
+
type Heading = {
|
|
4
4
|
title: string;
|
|
5
5
|
subtitle?: string;
|
|
6
6
|
logo: string;
|
|
7
|
-
}
|
|
7
|
+
};
|
|
8
|
+
export declare const htmlToPdf: (renderHtmlPass: (tocPages?: Map<string, number>) => Promise<string>, heading: Heading, outline: OutlineEntry[], verbose: (msg: string) => void, css: string, theme: ThemeMapping, paper?: string) => Promise<Buffer>;
|
|
9
|
+
export {};
|
|
@@ -78,6 +78,49 @@ const addOutline = async (doc, entries) => {
|
|
|
78
78
|
doc.catalog.set(PDFName.of("Outlines"), root);
|
|
79
79
|
doc.catalog.set(PDFName.of("PageMode"), PDFName.of("UseOutlines"));
|
|
80
80
|
};
|
|
81
|
+
/* render the header/footer templates for the page decoration: they
|
|
82
|
+
render in an isolated context, so they need their own inline styling,
|
|
83
|
+
the regular font face embedded as a data: URI, and the special
|
|
84
|
+
"pageNumber" class for the injected page number */
|
|
85
|
+
const decorationTemplates = (heading, css, theme, left, right) => {
|
|
86
|
+
const fontFace = css.match(/@font-face\s*\{[^}]*\}/)?.[0] ?? "";
|
|
87
|
+
const subtitle = heading.subtitle?.trim();
|
|
88
|
+
const headText = escapeHtml(heading.title) +
|
|
89
|
+
(subtitle !== undefined && subtitle !== "" ? ` — ${escapeHtml(subtitle)}` : "");
|
|
90
|
+
return {
|
|
91
|
+
headerTemplate: `<style>${fontFace}</style>` +
|
|
92
|
+
"<div style=\"width: 100%; margin-top: 0.8cm;\">" +
|
|
93
|
+
`<div style="margin: 0 ${right} 0 ${left}; ` +
|
|
94
|
+
"font-family: 'Source Sans 3', sans-serif; " +
|
|
95
|
+
`font-size: 8pt; color: ${theme.symbol}; border-bottom: 1px solid ${theme.border}; ` +
|
|
96
|
+
"padding-bottom: 1mm; display: flex; justify-content: space-between; " +
|
|
97
|
+
"align-items: flex-end;\">" +
|
|
98
|
+
`<span>${headText}</span>` +
|
|
99
|
+
`<img src="${escapeHtml(heading.logo)}" alt="" style="height: 3.5mm;"/>` +
|
|
100
|
+
"</div></div>",
|
|
101
|
+
footerTemplate: `<style>${fontFace}</style>` +
|
|
102
|
+
"<div style=\"width: 100%; margin-bottom: 0.8cm;\">" +
|
|
103
|
+
`<div style="margin: 0 ${right} 0 ${left}; ` +
|
|
104
|
+
"font-family: 'Source Sans 3', sans-serif; " +
|
|
105
|
+
`font-size: 8pt; color: ${theme.symbol}; display: flex; ` +
|
|
106
|
+
`justify-content: space-between; border-top: 1px solid ${theme.border}; ` +
|
|
107
|
+
"padding-top: 1mm;\">" +
|
|
108
|
+
`<span>${headText}</span>` +
|
|
109
|
+
`<span style="color: ${theme.muted}; font-weight: bold;" class="pageNumber"></span></div></div>`
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/* draw the vertical brand bar onto the left edge of the physical
|
|
113
|
+
paper of every page, as Chromium clips print content to the page
|
|
114
|
+
box (0.6rem at the 9pt print root) */
|
|
115
|
+
const drawBrandBar = async (doc, accent) => {
|
|
116
|
+
const { rgb } = await import("pdf-lib");
|
|
117
|
+
const hex = accent.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
118
|
+
const [r, g, b] = (hex !== null ? hex.slice(1) : ["00", "00", "00"])
|
|
119
|
+
.map((component) => parseInt(component, 16) / 255);
|
|
120
|
+
for (const page of doc.getPages())
|
|
121
|
+
page.drawRectangle({ x: 0, y: 0, width: 5.4,
|
|
122
|
+
height: page.getHeight(), color: rgb(r, g, b) });
|
|
123
|
+
};
|
|
81
124
|
/* render a self-contained HTML document into a PDF via Playwright,
|
|
82
125
|
re-rendering the HTML with the discovered ToC page numbers */
|
|
83
126
|
export const htmlToPdf = async (renderHtmlPass, heading, outline, verbose, css, theme, paper = paperSizeDefault) => {
|
|
@@ -85,11 +128,6 @@ export const htmlToPdf = async (renderHtmlPass, heading, outline, verbose, css,
|
|
|
85
128
|
/* the page geometry of the chosen paper size */
|
|
86
129
|
const setup = paperSetup(paper);
|
|
87
130
|
const margin = setup.margin;
|
|
88
|
-
/* the regular font face and the title/subtitle
|
|
89
|
-
text for the header/footer templates */
|
|
90
|
-
const fontFace = css.match(/@font-face\s*\{[^}]*\}/)?.[0] ?? "";
|
|
91
|
-
const headText = escapeHtml(heading.title) +
|
|
92
|
-
(heading.subtitle !== undefined ? ` — ${escapeHtml(heading.subtitle)}` : "");
|
|
93
131
|
/* launch the Playwright Chromium browser, falling back to a
|
|
94
132
|
system-installed Google Chrome if the Chromium download is missing */
|
|
95
133
|
let browser;
|
|
@@ -139,31 +177,10 @@ export const htmlToPdf = async (renderHtmlPass, heading, outline, verbose, css,
|
|
|
139
177
|
html = next;
|
|
140
178
|
plain = await renderPdf(html);
|
|
141
179
|
}
|
|
180
|
+
/* render the final document, decorated with header/footer */
|
|
142
181
|
const decorated = await renderPdf(html, {
|
|
143
|
-
/* the header/footer templates render in an isolated context,
|
|
144
|
-
so they need their own inline styling, the regular font face
|
|
145
|
-
embedded as a data: URI, and the special "title"/"pageNumber"
|
|
146
|
-
classes for the injected values */
|
|
147
182
|
displayHeaderFooter: true,
|
|
148
|
-
|
|
149
|
-
"<div style=\"width: 100%; margin-top: 0.8cm;\">" +
|
|
150
|
-
`<div style="margin: 0 ${paperLength(setup, margin.left)}; ` +
|
|
151
|
-
"font-family: 'Source Sans 3', sans-serif; " +
|
|
152
|
-
`font-size: 8pt; color: ${theme.symbol}; border-bottom: 1px solid ${theme.border}; ` +
|
|
153
|
-
"padding-bottom: 1mm; display: flex; justify-content: space-between; " +
|
|
154
|
-
"align-items: flex-end;\">" +
|
|
155
|
-
`<span>${headText}</span>` +
|
|
156
|
-
`<img src="${escapeHtml(heading.logo)}" alt="" style="height: 3.5mm;"/>` +
|
|
157
|
-
"</div></div>",
|
|
158
|
-
footerTemplate: `<style>${fontFace}</style>` +
|
|
159
|
-
"<div style=\"width: 100%; margin-bottom: 0.8cm;\">" +
|
|
160
|
-
`<div style="margin: 0 ${paperLength(setup, margin.left)}; ` +
|
|
161
|
-
"font-family: 'Source Sans 3', sans-serif; " +
|
|
162
|
-
`font-size: 8pt; color: ${theme.text}; display: flex; ` +
|
|
163
|
-
`justify-content: space-between; border-top: 1px solid ${theme.border}; ` +
|
|
164
|
-
"padding-top: 1mm;\">" +
|
|
165
|
-
`<span style="color: ${theme.symbol}">${headText}</span>` +
|
|
166
|
-
`<span style="color: ${theme.muted}; font-weight: bold;" class="pageNumber"></span></div></div>`
|
|
183
|
+
...decorationTemplates(heading, css, theme, paperLength(setup, margin.left), paperLength(setup, margin.right))
|
|
167
184
|
});
|
|
168
185
|
/* a title page carries no header/footer: as Chromium decorates
|
|
169
186
|
all pages unconditionally, swap the first page of the
|
|
@@ -171,7 +188,7 @@ export const htmlToPdf = async (renderHtmlPass, heading, outline, verbose, css,
|
|
|
171
188
|
the ToC page number iteration) into the decorated document
|
|
172
189
|
*in place*, which keeps the decorated document's link
|
|
173
190
|
destinations (and hence the internal hyperlinks) intact */
|
|
174
|
-
const { PDFDocument
|
|
191
|
+
const { PDFDocument } = await import("pdf-lib");
|
|
175
192
|
const merged = await PDFDocument.load(decorated);
|
|
176
193
|
if (html.includes("class=\"titlepage\"")) {
|
|
177
194
|
const source = await PDFDocument.load(plain);
|
|
@@ -182,15 +199,8 @@ export const htmlToPdf = async (renderHtmlPass, heading, outline, verbose, css,
|
|
|
182
199
|
/* attach the document outline for the viewer side-bar */
|
|
183
200
|
verbose("attaching PDF outline (bookmarks)");
|
|
184
201
|
await addOutline(merged, outline);
|
|
185
|
-
/*
|
|
186
|
-
|
|
187
|
-
content to the page box (0.6rem at the 9pt print root) */
|
|
188
|
-
const hex = theme.accent.match(/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
189
|
-
const [r, g, b] = (hex !== null ? hex.slice(1) : ["00", "00", "00"])
|
|
190
|
-
.map((component) => parseInt(component, 16) / 255);
|
|
191
|
-
for (const pdfPage of merged.getPages())
|
|
192
|
-
pdfPage.drawRectangle({ x: 0, y: 0, width: 5.4,
|
|
193
|
-
height: pdfPage.getHeight(), color: rgb(r, g, b) });
|
|
202
|
+
/* decorate every page with the vertical brand bar */
|
|
203
|
+
await drawBrandBar(merged, theme.accent);
|
|
194
204
|
return Buffer.from(await merged.save());
|
|
195
205
|
}
|
|
196
206
|
finally {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import { type Config as GradiaConfig } from "@rse/gradia";
|
|
3
|
-
export type
|
|
3
|
+
export type Schema = SchemaObject[];
|
|
4
4
|
export type SchemaObject = {
|
|
5
5
|
kind: string;
|
|
6
6
|
name?: string;
|
|
@@ -19,6 +19,7 @@ export type SchemaDiagram = {
|
|
|
19
19
|
edges?: string;
|
|
20
20
|
center?: string;
|
|
21
21
|
links?: "props" | "all";
|
|
22
|
+
edgeSource?: string;
|
|
22
23
|
edgeTarget?: string;
|
|
23
24
|
edgeArity?: string;
|
|
24
25
|
hierarchy?: boolean;
|
|
@@ -39,4 +40,4 @@ export type SchemaProperty = {
|
|
|
39
40
|
value?: string;
|
|
40
41
|
optional?: boolean;
|
|
41
42
|
};
|
|
42
|
-
export declare const
|
|
43
|
+
export declare const Schema: v.GenericSchema<Schema>;
|
|
@@ -8,9 +8,12 @@ import { Gradia } from "@rse/gradia";
|
|
|
8
8
|
/* ==== Schema ==== */
|
|
9
9
|
/* the Gradia rendering options, derived from the configuration
|
|
10
10
|
defaults of Gradia: the option names constrain the allowed keys and
|
|
11
|
-
the types of the default values constrain the allowed value types
|
|
11
|
+
the types of the default values constrain the allowed value types
|
|
12
|
+
(with the numbers restricted to the finite, non-negative values
|
|
13
|
+
Gradia honors, as it silently drops all others) */
|
|
12
14
|
const GradiaConfigSchemas = Object.fromEntries(Object.entries(Gradia.config).map(([key, value]) => [key,
|
|
13
|
-
typeof value === "boolean" ? v.boolean() :
|
|
15
|
+
typeof value === "boolean" ? v.boolean() :
|
|
16
|
+
typeof value === "number" ? v.pipe(v.number(), v.finite(), v.minValue(0)) : v.string()]));
|
|
14
17
|
const SchemaDiagramConfig = v.partial(v.strictObject(GradiaConfigSchemas, (issue) => issue.expected === "never" ? "unknown Gradia rendering option" :
|
|
15
18
|
"expected a map of Gradia rendering options"));
|
|
16
19
|
const SchemaProperty = v.strictObject({
|
|
@@ -25,6 +28,7 @@ const SchemaDiagram = v.strictObject({
|
|
|
25
28
|
edges: v.optional(v.string()),
|
|
26
29
|
center: v.optional(v.string()),
|
|
27
30
|
links: v.optional(v.picklist(["props", "all"])),
|
|
31
|
+
edgeSource: v.optional(v.string()),
|
|
28
32
|
edgeTarget: v.optional(v.string()),
|
|
29
33
|
edgeArity: v.optional(v.string()),
|
|
30
34
|
hierarchy: v.optional(v.boolean()),
|
|
@@ -36,7 +40,7 @@ const SchemaDiagram = v.strictObject({
|
|
|
36
40
|
});
|
|
37
41
|
const SchemaFormat = v.strictObject({
|
|
38
42
|
type: v.optional(v.picklist(["auto", "complex", "concise"])),
|
|
39
|
-
maxTableColumns: v.optional(v.pipe(v.number(), v.integer(), v.minValue(
|
|
43
|
+
maxTableColumns: v.optional(v.pipe(v.number(), v.integer(), v.minValue(2))),
|
|
40
44
|
withUnusedProps: v.optional(v.boolean())
|
|
41
45
|
});
|
|
42
46
|
const SchemaObject = v.strictObject({
|
|
@@ -51,4 +55,4 @@ const SchemaObject = v.strictObject({
|
|
|
51
55
|
props: v.optional(v.array(SchemaProperty)),
|
|
52
56
|
childs: v.optional(v.array(v.lazy(() => SchemaObject)))
|
|
53
57
|
});
|
|
54
|
-
export const
|
|
58
|
+
export const Schema = v.pipe(v.array(SchemaObject), v.minLength(1));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export type Spec = {
|
|
3
|
+
artifacts: SpecArtifact[];
|
|
4
|
+
};
|
|
5
|
+
export type SpecArtifact = {
|
|
6
|
+
created: Date;
|
|
7
|
+
modified: Date;
|
|
8
|
+
objects: SpecObject[];
|
|
9
|
+
};
|
|
10
|
+
export type SpecObject = {
|
|
11
|
+
kind: string;
|
|
12
|
+
id: string;
|
|
13
|
+
anchor?: string;
|
|
14
|
+
paren?: string;
|
|
15
|
+
name: string;
|
|
16
|
+
primary?: boolean;
|
|
17
|
+
description?: SpecDescription;
|
|
18
|
+
properties: SpecProperty[];
|
|
19
|
+
childs: SpecObject[];
|
|
20
|
+
};
|
|
21
|
+
export type SpecDescription = {
|
|
22
|
+
description: string;
|
|
23
|
+
rationale?: string;
|
|
24
|
+
embedding?: string[];
|
|
25
|
+
};
|
|
26
|
+
export type SpecProperty = {
|
|
27
|
+
key: string;
|
|
28
|
+
value: string;
|
|
29
|
+
embedding?: string[];
|
|
30
|
+
};
|
|
31
|
+
export declare const Spec: v.GenericSchema<Spec>;
|
|
@@ -5,32 +5,32 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as v from "valibot";
|
|
7
7
|
/* ==== Schema ==== */
|
|
8
|
-
const
|
|
8
|
+
const SpecProperty = v.object({
|
|
9
9
|
key: v.string(),
|
|
10
10
|
value: v.string(),
|
|
11
11
|
embedding: v.optional(v.array(v.string()))
|
|
12
12
|
});
|
|
13
|
-
const
|
|
13
|
+
const SpecDescription = v.object({
|
|
14
14
|
description: v.string(),
|
|
15
15
|
rationale: v.optional(v.string()),
|
|
16
16
|
embedding: v.optional(v.array(v.string()))
|
|
17
17
|
});
|
|
18
|
-
const
|
|
18
|
+
const SpecObject = v.object({
|
|
19
19
|
kind: v.string(),
|
|
20
20
|
id: v.string(),
|
|
21
21
|
anchor: v.optional(v.string()),
|
|
22
22
|
paren: v.optional(v.string()),
|
|
23
23
|
name: v.string(),
|
|
24
24
|
primary: v.optional(v.boolean()),
|
|
25
|
-
description: v.optional(
|
|
26
|
-
properties: v.array(
|
|
27
|
-
childs: v.array(v.lazy(() =>
|
|
25
|
+
description: v.optional(SpecDescription),
|
|
26
|
+
properties: v.array(SpecProperty),
|
|
27
|
+
childs: v.array(v.lazy(() => SpecObject))
|
|
28
28
|
});
|
|
29
|
-
const
|
|
29
|
+
const SpecArtifact = v.object({
|
|
30
30
|
created: v.date(),
|
|
31
31
|
modified: v.date(),
|
|
32
|
-
objects: v.pipe(v.array(
|
|
32
|
+
objects: v.pipe(v.array(SpecObject), v.minLength(1))
|
|
33
33
|
});
|
|
34
|
-
export const
|
|
35
|
-
artifacts: v.pipe(v.array(
|
|
34
|
+
export const Spec = v.object({
|
|
35
|
+
artifacts: v.pipe(v.array(SpecArtifact), v.minLength(1))
|
|
36
36
|
});
|