@dooc-dev/dooc 0.0.0-stage → 0.0.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 (213) hide show
  1. package/README.md +26 -2
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +244 -0
  4. package/dist/code.global.js +285 -0
  5. package/dist/compiler/bundle-urls.d.ts +3 -0
  6. package/dist/compiler/bundle-urls.js +294 -0
  7. package/dist/compiler/bundle.d.ts +2 -0
  8. package/dist/compiler/bundle.js +100 -0
  9. package/dist/compiler/check-prose.d.ts +11 -0
  10. package/dist/compiler/check-prose.js +138 -0
  11. package/dist/compiler/check-types.d.ts +44 -0
  12. package/dist/compiler/check-types.js +1 -0
  13. package/dist/compiler/check.d.ts +5 -0
  14. package/dist/compiler/check.js +450 -0
  15. package/dist/compiler/code-blocks.d.ts +17 -0
  16. package/dist/compiler/code-blocks.js +198 -0
  17. package/dist/compiler/code-languages.d.ts +10 -0
  18. package/dist/compiler/code-languages.js +109 -0
  19. package/dist/compiler/compile.d.ts +95 -0
  20. package/dist/compiler/compile.js +410 -0
  21. package/dist/compiler/config.d.ts +7 -0
  22. package/dist/compiler/config.js +52 -0
  23. package/dist/compiler/dialect.d.ts +24 -0
  24. package/dist/compiler/dialect.js +198 -0
  25. package/dist/compiler/drawio.d.ts +26 -0
  26. package/dist/compiler/drawio.js +176 -0
  27. package/dist/compiler/figures.d.ts +14 -0
  28. package/dist/compiler/figures.js +221 -0
  29. package/dist/compiler/file-cache.d.ts +14 -0
  30. package/dist/compiler/file-cache.js +61 -0
  31. package/dist/compiler/files.d.ts +29 -0
  32. package/dist/compiler/files.js +431 -0
  33. package/dist/compiler/fonts.d.ts +10 -0
  34. package/dist/compiler/fonts.js +54 -0
  35. package/dist/compiler/format.d.ts +1 -0
  36. package/dist/compiler/format.js +42 -0
  37. package/dist/compiler/from-mdast.d.ts +16 -0
  38. package/dist/compiler/from-mdast.js +142 -0
  39. package/dist/compiler/icons.d.ts +14 -0
  40. package/dist/compiler/icons.js +85 -0
  41. package/dist/compiler/language-codes.d.ts +7 -0
  42. package/dist/compiler/language-codes.js +17 -0
  43. package/dist/compiler/locales.d.ts +7 -0
  44. package/dist/compiler/locales.js +67 -0
  45. package/dist/compiler/manifest.d.ts +6 -0
  46. package/dist/compiler/manifest.js +30 -0
  47. package/dist/compiler/ops.d.ts +44 -0
  48. package/dist/compiler/ops.js +362 -0
  49. package/dist/compiler/public-file.d.ts +2 -0
  50. package/dist/compiler/public-file.js +93 -0
  51. package/dist/compiler/serialize.d.ts +3 -0
  52. package/dist/compiler/serialize.js +16 -0
  53. package/dist/compiler/terms.d.ts +9 -0
  54. package/dist/compiler/terms.js +44 -0
  55. package/dist/compiler/textfile-remote.d.ts +12 -0
  56. package/dist/compiler/textfile-remote.js +124 -0
  57. package/dist/compiler/textfile.d.ts +9 -0
  58. package/dist/compiler/textfile.js +113 -0
  59. package/dist/compiler/theme-variants.d.ts +4 -0
  60. package/dist/compiler/theme-variants.js +22 -0
  61. package/dist/compiler/zip.d.ts +6 -0
  62. package/dist/compiler/zip.js +48 -0
  63. package/dist/core/blocks.d.ts +99 -0
  64. package/dist/core/blocks.js +29 -0
  65. package/dist/core/code-file.d.ts +12 -0
  66. package/dist/core/code-file.js +68 -0
  67. package/dist/core/content-metadata.d.ts +48 -0
  68. package/dist/core/content-metadata.js +264 -0
  69. package/dist/core/content-types.d.ts +45 -0
  70. package/dist/core/content-types.js +1 -0
  71. package/dist/core/figure-frame.d.ts +12 -0
  72. package/dist/core/figure-frame.js +108 -0
  73. package/dist/core/file-embed.d.ts +13 -0
  74. package/dist/core/file-embed.js +43 -0
  75. package/dist/core/files.d.ts +15 -0
  76. package/dist/core/files.js +59 -0
  77. package/dist/core/flow-base-css.generated.d.ts +1 -0
  78. package/dist/core/flow-base-css.generated.js +4 -0
  79. package/dist/core/flow-css.d.ts +9 -0
  80. package/dist/core/flow-css.js +54 -0
  81. package/dist/core/graph-common.d.ts +32 -0
  82. package/dist/core/graph-common.js +113 -0
  83. package/dist/core/graph.d.ts +117 -0
  84. package/dist/core/graph.js +480 -0
  85. package/dist/core/html.d.ts +29 -0
  86. package/dist/core/html.js +444 -0
  87. package/dist/core/icons.d.ts +1 -0
  88. package/dist/core/icons.js +1 -0
  89. package/dist/core/inline.d.ts +27 -0
  90. package/dist/core/inline.js +42 -0
  91. package/dist/core/language.d.ts +41 -0
  92. package/dist/core/language.js +44 -0
  93. package/dist/core/languages/ja.d.ts +2 -0
  94. package/dist/core/languages/ja.js +5 -0
  95. package/dist/core/markdown.d.ts +27 -0
  96. package/dist/core/markdown.js +154 -0
  97. package/dist/core/previewers.d.ts +37 -0
  98. package/dist/core/previewers.js +103 -0
  99. package/dist/core/routing.d.ts +20 -0
  100. package/dist/core/routing.js +64 -0
  101. package/dist/core/schema-comments.d.ts +14 -0
  102. package/dist/core/schema-comments.js +52 -0
  103. package/dist/core/schema-graph-flow.d.ts +9 -0
  104. package/dist/core/schema-graph-flow.js +379 -0
  105. package/dist/core/schema-graph-mount.d.ts +9 -0
  106. package/dist/core/schema-graph-mount.js +105 -0
  107. package/dist/core/schema-graph.d.ts +254 -0
  108. package/dist/core/schema-graph.js +981 -0
  109. package/dist/core/table-ui.d.ts +2 -0
  110. package/dist/core/table-ui.js +180 -0
  111. package/dist/core/taxonomy.d.ts +12 -0
  112. package/dist/core/taxonomy.js +49 -0
  113. package/dist/core/term-reference.d.ts +5 -0
  114. package/dist/core/term-reference.js +19 -0
  115. package/dist/core/terms-ui.d.ts +6 -0
  116. package/dist/core/terms-ui.js +22 -0
  117. package/dist/core/textfile.d.ts +10 -0
  118. package/dist/core/textfile.js +39 -0
  119. package/dist/core/theme-variants.d.ts +3 -0
  120. package/dist/core/theme-variants.js +37 -0
  121. package/dist/core/toc-ui.d.ts +2 -0
  122. package/dist/core/toc-ui.js +161 -0
  123. package/dist/core/video-ui.d.ts +2 -0
  124. package/dist/core/video-ui.js +253 -0
  125. package/dist/core/workspace.d.ts +15 -0
  126. package/dist/core/workspace.js +28 -0
  127. package/dist/edit.global.js +305 -0
  128. package/dist/flow.global.js +4959 -0
  129. package/dist/index.d.ts +17 -0
  130. package/dist/index.js +31 -0
  131. package/dist/mcp.d.ts +24 -0
  132. package/dist/mcp.js +467 -0
  133. package/dist/plugins/actions.d.ts +45 -0
  134. package/dist/plugins/actions.js +125 -0
  135. package/dist/plugins/blocks.d.ts +92 -0
  136. package/dist/plugins/blocks.js +99 -0
  137. package/dist/plugins/index.d.ts +4 -0
  138. package/dist/plugins/index.js +1 -0
  139. package/dist/plugins/oauth.d.ts +14 -0
  140. package/dist/plugins/oauth.js +164 -0
  141. package/dist/plugins/runtime.d.ts +24 -0
  142. package/dist/plugins/runtime.js +339 -0
  143. package/dist/plugins/types.d.ts +56 -0
  144. package/dist/plugins/types.js +1 -0
  145. package/dist/plugins/view.d.ts +29 -0
  146. package/dist/plugins/view.js +60 -0
  147. package/dist/ui.global.js +77 -0
  148. package/dist/viewer/assets.d.ts +2 -0
  149. package/dist/viewer/assets.js +3 -0
  150. package/dist/viewer/build.d.ts +12 -0
  151. package/dist/viewer/build.js +55 -0
  152. package/dist/viewer/bundle-network.d.ts +2 -0
  153. package/dist/viewer/bundle-network.js +88 -0
  154. package/dist/viewer/code-file.d.ts +20 -0
  155. package/dist/viewer/code-file.js +42 -0
  156. package/dist/viewer/editor-tools.d.ts +38 -0
  157. package/dist/viewer/editor-tools.js +211 -0
  158. package/dist/viewer/file-references.d.ts +14 -0
  159. package/dist/viewer/file-references.js +38 -0
  160. package/dist/viewer/font-picker.d.ts +4 -0
  161. package/dist/viewer/font-picker.js +145 -0
  162. package/dist/viewer/font-settings.d.ts +11 -0
  163. package/dist/viewer/font-settings.js +50 -0
  164. package/dist/viewer/fonts/LICENSE.txt +119 -0
  165. package/dist/viewer/fonts/udev-gothic-regular.woff2 +0 -0
  166. package/dist/viewer/fonts.d.ts +2 -0
  167. package/dist/viewer/fonts.js +13 -0
  168. package/dist/viewer/git-diff.d.ts +3 -0
  169. package/dist/viewer/git-diff.js +33 -0
  170. package/dist/viewer/git-types.d.ts +73 -0
  171. package/dist/viewer/git-types.js +1 -0
  172. package/dist/viewer/git.d.ts +5 -0
  173. package/dist/viewer/git.js +337 -0
  174. package/dist/viewer/hmr.d.ts +53 -0
  175. package/dist/viewer/hmr.js +335 -0
  176. package/dist/viewer/i18n.d.ts +6 -0
  177. package/dist/viewer/i18n.js +65 -0
  178. package/dist/viewer/ja-lint.d.ts +3 -0
  179. package/dist/viewer/ja-lint.js +44 -0
  180. package/dist/viewer/language-picker.d.ts +2 -0
  181. package/dist/viewer/language-picker.js +233 -0
  182. package/dist/viewer/languages.d.ts +11 -0
  183. package/dist/viewer/languages.js +20 -0
  184. package/dist/viewer/locales/eng.json +294 -0
  185. package/dist/viewer/locales/jpn.json +294 -0
  186. package/dist/viewer/locales/zho.json +294 -0
  187. package/dist/viewer/nav-focus.d.ts +2 -0
  188. package/dist/viewer/nav-focus.js +64 -0
  189. package/dist/viewer/plugin-blocks.d.ts +3 -0
  190. package/dist/viewer/plugin-blocks.js +115 -0
  191. package/dist/viewer/plugin-config.d.ts +35 -0
  192. package/dist/viewer/plugin-config.js +57 -0
  193. package/dist/viewer/plugin-view.d.ts +3 -0
  194. package/dist/viewer/plugin-view.js +28 -0
  195. package/dist/viewer/plugins.d.ts +8 -0
  196. package/dist/viewer/plugins.js +214 -0
  197. package/dist/viewer/resize.d.ts +4 -0
  198. package/dist/viewer/resize.js +238 -0
  199. package/dist/viewer/resource-library.d.ts +29 -0
  200. package/dist/viewer/resource-library.js +104 -0
  201. package/dist/viewer/search.d.ts +16 -0
  202. package/dist/viewer/search.js +358 -0
  203. package/dist/viewer/serve.d.ts +206 -0
  204. package/dist/viewer/serve.js +3233 -0
  205. package/dist/viewer/site-icon.d.ts +8 -0
  206. package/dist/viewer/site-icon.js +56 -0
  207. package/dist/viewer/textfile.d.ts +9 -0
  208. package/dist/viewer/textfile.js +50 -0
  209. package/dist/viewer/theme-picker.d.ts +2 -0
  210. package/dist/viewer/theme-picker.js +135 -0
  211. package/dist/viewer/workspace.d.ts +4 -0
  212. package/dist/viewer/workspace.js +47 -0
  213. package/package.json +127 -5
@@ -0,0 +1,27 @@
1
+ import type { Image, Link, List as MdList, PhrasingContent, Root, RootContent } from "mdast";
2
+ import type { Block, ListItem } from "./blocks.js";
3
+ export declare const parseMdast: (raw: string) => Root;
4
+ /** File previews occupy whole paragraphs; prose, lists and tables keep their inline layout. */
5
+ export declare const standaloneImages: (node: RootContent) => Image[];
6
+ export declare const embeddedImages: (root: Root) => Image[];
7
+ export declare const offsetAt: (body: string, line: number, column: number) => number;
8
+ /** A closed fenced block. Offsets are UTF-16 offsets in the editor body. */
9
+ export declare function codeBlockAt(body: string, position: number): {
10
+ start: number;
11
+ end: number;
12
+ topLevel: boolean;
13
+ meta: string;
14
+ from: number;
15
+ to: number;
16
+ language: string;
17
+ code: string;
18
+ indent: string;
19
+ eol: string;
20
+ marker: string;
21
+ offsets: number[];
22
+ };
23
+ /** Keep inline content as standard Markdown, including escapes and nested emphasis. */
24
+ export declare const inlineToText: (nodes: PhrasingContent[]) => string;
25
+ export declare const listItems: (n: MdList, convert?: (nodes: RootContent[]) => Block[]) => ListItem[];
26
+ /** Links inside callouts are authored Markdown; other code fences remain opaque. */
27
+ export declare const contentLinks: (root: Root) => Link[];
@@ -0,0 +1,154 @@
1
+ import remarkFrontmatter from "remark-frontmatter";
2
+ import remarkGfm from "remark-gfm";
3
+ import remarkParse from "remark-parse";
4
+ import remarkStringify from "remark-stringify";
5
+ import { unified } from "unified";
6
+ import { visit } from "unist-util-visit";
7
+ import { parseFigureLink } from "./file-embed.js";
8
+ import { parseDocLink } from "./routing.js";
9
+ import { parseTermReference, termFileUrl } from "./term-reference.js";
10
+ const processor = unified().use(remarkParse).use(remarkFrontmatter).use(remarkGfm);
11
+ const inlinePrinter = unified().use(remarkGfm).use(remarkStringify);
12
+ export const parseMdast = (raw) => processor.parse(raw);
13
+ /** File previews occupy whole paragraphs; prose, lists and tables keep their inline layout. */
14
+ export const standaloneImages = (node) => node.type === "paragraph" &&
15
+ node.children.every((child) => child.type === "image" || (child.type === "text" && !child.value.trim()))
16
+ ? node.children.filter((child) => child.type === "image")
17
+ : [];
18
+ export const embeddedImages = (root) => {
19
+ const images = [];
20
+ visit(root, "paragraph", (node, _index, parent) => {
21
+ if (parent?.type === "root" || parent?.type === "blockquote")
22
+ images.push(...standaloneImages(node));
23
+ });
24
+ return images;
25
+ };
26
+ export const offsetAt = (body, line, column) => body
27
+ .split("\n")
28
+ .slice(0, line - 1)
29
+ .reduce((offset, text) => offset + text.length + 1, 0) +
30
+ column -
31
+ 1;
32
+ /** A closed fenced block. Offsets are UTF-16 offsets in the editor body. */
33
+ export function codeBlockAt(body, position) {
34
+ if (!Number.isInteger(position) || position < 0 || position > body.length) {
35
+ throw new Error("Invalid cursor position");
36
+ }
37
+ const tree = parseMdast(body);
38
+ let block;
39
+ visit(tree, (node, _index, parent) => {
40
+ if (node.type !== "code")
41
+ return;
42
+ const start = node.position
43
+ ? offsetAt(body, node.position.start.line, node.position.start.column)
44
+ : undefined;
45
+ const end = node.position
46
+ ? offsetAt(body, node.position.end.line, node.position.end.column)
47
+ : undefined;
48
+ if (start == null || end == null || position < start || position > end)
49
+ return;
50
+ const raw = body.slice(start, end);
51
+ const opening = /^( {0,3})(`{3,}|~{3,})[^\r\n]*\r?\n/.exec(raw);
52
+ if (!opening?.[2])
53
+ throw new Error("Select a fenced code block");
54
+ const closing = /(?:^|\n)([ \t>]*)(`{3,}|~{3,})[ \t]*$/.exec(raw);
55
+ if (!closing?.[2] ||
56
+ closing[2][0] !== opening[2][0] ||
57
+ closing[2].length < opening[2].length)
58
+ throw new Error("Close the code fence before running a tool");
59
+ const from = start + opening[0].length;
60
+ const to = start + closing.index + (raw[closing.index] === "\n" ? 1 : 0);
61
+ if (to < from)
62
+ throw new Error("Invalid code fence");
63
+ const offsets = [];
64
+ let offset = from;
65
+ const lines = body.slice(from, to).split(/\r?\n/);
66
+ lines.pop();
67
+ const values = node.value.replaceAll("\r\n", "\n").split("\n");
68
+ for (const [index, line] of lines.entries()) {
69
+ const value = values[index] ?? "";
70
+ if (!line.endsWith(value))
71
+ throw new Error("Unsupported code indentation");
72
+ offsets.push(offset + line.length - value.length);
73
+ offset += line.length + (body[offset + line.length] === "\r" ? 2 : 1);
74
+ }
75
+ const code = node.value.replaceAll("\r\n", "\n");
76
+ if (!offsets.length)
77
+ offsets.push(from);
78
+ block = {
79
+ start,
80
+ end,
81
+ topLevel: parent?.type === "root",
82
+ meta: node.meta ?? "",
83
+ from,
84
+ to,
85
+ language: node.lang ?? "",
86
+ code,
87
+ indent: closing[1] ?? "",
88
+ eol: opening[0].endsWith("\r\n") ? "\r\n" : "\n",
89
+ marker: opening[2],
90
+ offsets,
91
+ };
92
+ });
93
+ if (!block)
94
+ throw new Error("Place the cursor inside a fenced code block");
95
+ return block;
96
+ }
97
+ /** Keep inline content as standard Markdown, including escapes and nested emphasis. */
98
+ export const inlineToText = (nodes) => {
99
+ const root = {
100
+ type: "root",
101
+ children: [{ type: "paragraph", children: structuredClone(nodes) }],
102
+ };
103
+ visit(root, "link", (node) => {
104
+ try {
105
+ const file = parseTermReference(node.url);
106
+ if (file)
107
+ node.url = termFileUrl(file);
108
+ const ref = parseDocLink(node.url) ?? parseFigureLink(node.url);
109
+ if (ref)
110
+ node.url =
111
+ termFileUrl(ref.file) +
112
+ (ref.fragment ? `#${encodeURIComponent(ref.fragment)}` : "");
113
+ }
114
+ catch {
115
+ /* Reported by validation. */
116
+ }
117
+ });
118
+ return inlinePrinter.stringify(root).replace(/\n$/, "");
119
+ };
120
+ export const listItems = (n, convert) => n.children.map((li) => {
121
+ if (convert && li.children.some((child) => child.type === "blockquote"))
122
+ return { blocks: convert(li.children) };
123
+ const text = li.children
124
+ .filter((c) => c.type === "paragraph")
125
+ .map((c) => inlineToText(c.children))
126
+ .join("");
127
+ const nested = li.children.filter((c) => c.type === "list");
128
+ if (nested.length === 0) {
129
+ return text;
130
+ }
131
+ return {
132
+ text,
133
+ children: nested.flatMap((c) => listItems(c, convert)),
134
+ ...(nested[0]?.ordered ? { ordered: true } : {}),
135
+ };
136
+ });
137
+ /** Links inside callouts are authored Markdown; other code fences remain opaque. */
138
+ export const contentLinks = (root) => {
139
+ const links = [];
140
+ visit(root, (node) => {
141
+ if (node.type === "link")
142
+ links.push(node);
143
+ if (node.type === "code" && node.lang === "callouth") {
144
+ for (const link of contentLinks(parseMdast(node.value))) {
145
+ if (link.position) {
146
+ link.position.start.line += node.position?.start.line ?? 0;
147
+ link.position.end.line += node.position?.start.line ?? 0;
148
+ }
149
+ links.push(link);
150
+ }
151
+ }
152
+ });
153
+ return links;
154
+ };
@@ -0,0 +1,37 @@
1
+ export declare const PREVIEWERS: readonly [{
2
+ readonly id: "table";
3
+ readonly label: "Table";
4
+ readonly extensions: readonly ["csv", "tsv"];
5
+ }, {
6
+ readonly id: "image";
7
+ readonly label: "Image";
8
+ readonly extensions: readonly ["png", "jpg", "jpeg", "webp", "gif", "svg"];
9
+ }, {
10
+ readonly id: "html";
11
+ readonly label: "HTML";
12
+ readonly extensions: readonly ["html", "htm"];
13
+ }, {
14
+ readonly id: "markdown";
15
+ readonly label: "Markdown";
16
+ readonly extensions: readonly ["md", "markdown"];
17
+ }, {
18
+ readonly id: "code";
19
+ readonly label: "Code";
20
+ readonly extensions: readonly ["txt", "log", "json", "yaml", "yml", "toml", "xml", "css", "js", "jsx", "ts", "tsx", "mjs", "cjs", "py", "rs", "go", "java", "c", "h", "cpp", "hpp", "cs", "rb", "php", "swift", "kt", "sh", "bash", "ps1", "sql", "tdl"];
21
+ }, {
22
+ readonly id: "video";
23
+ readonly label: "Video";
24
+ readonly extensions: readonly ["mp4", "webm"];
25
+ }];
26
+ export type PreviewerId = (typeof PREVIEWERS)[number]["id"];
27
+ export interface PreviewSettings {
28
+ domains: string[];
29
+ }
30
+ export declare const defaultPreviewSettings: () => PreviewSettings;
31
+ /** Exact DNS names only: no schemes, credentials, ports, wildcard or implicit subdomains. */
32
+ export declare const normalizePreviewDomain: (input: string) => string;
33
+ /** Application-owned policy; document sources cannot grant network access. */
34
+ export declare const parsePreviewSettings: (input: unknown) => PreviewSettings;
35
+ export declare const allowedPreviewUrl: (source: string, domains: readonly string[]) => URL;
36
+ /** Selects a registered previewer only; reading files and executing content are separate. */
37
+ export declare const resolvePreviewer: (source: string, settings?: PreviewSettings) => PreviewerId | undefined;
@@ -0,0 +1,103 @@
1
+ import { parseFileReference } from "./textfile.js";
2
+ export const PREVIEWERS = [
3
+ { id: "table", label: "Table", extensions: ["csv", "tsv"] },
4
+ { id: "image", label: "Image", extensions: ["png", "jpg", "jpeg", "webp", "gif", "svg"] },
5
+ { id: "html", label: "HTML", extensions: ["html", "htm"] },
6
+ { id: "markdown", label: "Markdown", extensions: ["md", "markdown"] },
7
+ {
8
+ id: "code",
9
+ label: "Code",
10
+ extensions: [
11
+ "txt",
12
+ "log",
13
+ "json",
14
+ "yaml",
15
+ "yml",
16
+ "toml",
17
+ "xml",
18
+ "css",
19
+ "js",
20
+ "jsx",
21
+ "ts",
22
+ "tsx",
23
+ "mjs",
24
+ "cjs",
25
+ "py",
26
+ "rs",
27
+ "go",
28
+ "java",
29
+ "c",
30
+ "h",
31
+ "cpp",
32
+ "hpp",
33
+ "cs",
34
+ "rb",
35
+ "php",
36
+ "swift",
37
+ "kt",
38
+ "sh",
39
+ "bash",
40
+ "ps1",
41
+ "sql",
42
+ "tdl",
43
+ ],
44
+ },
45
+ { id: "video", label: "Video", extensions: ["mp4", "webm"] },
46
+ ];
47
+ export const defaultPreviewSettings = () => ({
48
+ domains: [],
49
+ });
50
+ /** Exact DNS names only: no schemes, credentials, ports, wildcard or implicit subdomains. */
51
+ export const normalizePreviewDomain = (input) => {
52
+ const domain = input.trim().toLowerCase();
53
+ if (domain.length > 253 ||
54
+ !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(domain) ||
55
+ /\.(?:localhost|local|internal|invalid|test)$/.test(domain))
56
+ throw new Error(`Invalid public domain: ${input}`);
57
+ return domain;
58
+ };
59
+ /** Application-owned policy; document sources cannot grant network access. */
60
+ export const parsePreviewSettings = (input) => {
61
+ if (!input || typeof input !== "object" || Array.isArray(input))
62
+ throw new Error("previews must be an object");
63
+ const value = input;
64
+ for (const key of Object.keys(value))
65
+ if (key !== "domains")
66
+ throw new Error(`Unknown previews field: ${key}`);
67
+ const settings = defaultPreviewSettings();
68
+ if (value.domains !== undefined) {
69
+ if (!Array.isArray(value.domains) || value.domains.some((d) => typeof d !== "string"))
70
+ throw new Error("previews.domains must be a list of domains");
71
+ settings.domains = value.domains.map(normalizePreviewDomain);
72
+ if (new Set(settings.domains).size !== settings.domains.length)
73
+ throw new Error("Duplicate allowed domain");
74
+ }
75
+ return settings;
76
+ };
77
+ export const allowedPreviewUrl = (source, domains) => {
78
+ const url = new URL(source);
79
+ if (url.protocol !== "https:" ||
80
+ url.username ||
81
+ url.password ||
82
+ url.port ||
83
+ /[\\\s]/.test(source))
84
+ throw new Error("External previews require an HTTPS URL without credentials or a custom port");
85
+ if (!domains.includes(url.hostname))
86
+ throw new Error(`Domain is not allowed: ${url.hostname}`);
87
+ return url;
88
+ };
89
+ /** Selects a registered previewer only; reading files and executing content are separate. */
90
+ export const resolvePreviewer = (source, settings = defaultPreviewSettings()) => {
91
+ let path;
92
+ try {
93
+ path =
94
+ /^[a-z][a-z\d+.-]*:/i.test(source) || source.startsWith("//")
95
+ ? decodeURIComponent(allowedPreviewUrl(source, settings.domains).pathname)
96
+ : parseFileReference(source).file;
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ const name = path.split("/").at(-1)?.toLowerCase() ?? "";
102
+ return PREVIEWERS.find((previewer) => previewer.extensions.some((extension) => name.endsWith(`.${extension}`)))?.id;
103
+ };
@@ -0,0 +1,20 @@
1
+ import type { CompiledDoc } from "./content-types.js";
2
+ /** The minimal doc reference routing needs (a shape CompiledDoc satisfies). */
3
+ export type DocRef = Pick<CompiledDoc, "id" | "parent">;
4
+ /** Parse `<docId>` (leading/trailing `/` are ignored). undefined if the shape differs. */
5
+ export declare const parseRoutePath: (path: string) => string | undefined;
6
+ /** Document links use paths relative to the docs root and optional heading fragments. */
7
+ export declare const parseDocLink: (url: string) => {
8
+ file: string;
9
+ fragment: string;
10
+ } | undefined;
11
+ /** Docs directly under parent (keeps input order = manifest order). parent omitted = top level. */
12
+ export declare const childrenOf: <D extends DocRef>(docs: readonly D[], parent?: string) => D[];
13
+ /** Ancestors (ordered top level → parent, excluding self). For breadcrumbs. */
14
+ export declare const ancestorsOf: <D extends DocRef>(docs: readonly D[], id: string) => D[];
15
+ /** DOM anchor id for a heading — namespaced by doc id. */
16
+ export declare const headingAnchor: (docId: string, slug: string) => string;
17
+ /** Canonical page URL: manifest ancestors form the path; headings use a separate fragment. */
18
+ export declare const docPath: (docs: readonly DocRef[], id: string) => string;
19
+ /** Resolve only the complete hierarchy path; no legacy single-id aliases. */
20
+ export declare const resolveDocPath: (docs: readonly DocRef[], path: string) => string | undefined;
@@ -0,0 +1,64 @@
1
+ // SoT for URL / id resolution logic (browser-safe, no node dependencies). Consumers resolve docs
2
+ // through here and never build paths themselves — a consumer is a display-only wrapper, and this
3
+ // package owns routing.
4
+ //
5
+ // Page URLs follow the manifest hierarchy; heading fragments stay separate.
6
+ // Body links reference source files; page URLs use the manifest hierarchy.
7
+ import { isDocId } from "./content-metadata.js";
8
+ import { localFile } from "./files.js";
9
+ import { parseFileReference } from "./textfile.js";
10
+ /** Parse `<docId>` (leading/trailing `/` are ignored). undefined if the shape differs. */
11
+ export const parseRoutePath = (path) => {
12
+ const seg = path.split("/").filter((s) => s !== "");
13
+ const [id] = seg;
14
+ return id != null && seg.length === 1 && isDocId(id) ? id : undefined;
15
+ };
16
+ /** Document links use paths relative to the docs root and optional heading fragments. */
17
+ export const parseDocLink = (url) => {
18
+ if (/^(?:[a-z][a-z\d+.-]*:|\/)/i.test(url))
19
+ return undefined;
20
+ const path = url.split(/[?#]/)[0] ?? "";
21
+ let decoded;
22
+ try {
23
+ decoded = decodeURIComponent(path);
24
+ }
25
+ catch {
26
+ throw new Error("Invalid file link encoding");
27
+ }
28
+ if (!decoded.toLowerCase().endsWith(".md"))
29
+ return undefined;
30
+ const ref = parseFileReference(url);
31
+ const file = ref.file.replace(/^(\.\/)+/, "");
32
+ if (!localFile(file))
33
+ throw new Error("Document links require a file inside the docs root");
34
+ return { file, fragment: ref.range?.slice(1) ?? "" };
35
+ };
36
+ /** Docs directly under parent (keeps input order = manifest order). parent omitted = top level. */
37
+ export const childrenOf = (docs, parent) => docs.filter((d) => d.parent === parent);
38
+ /** Ancestors (ordered top level → parent, excluding self). For breadcrumbs. */
39
+ export const ancestorsOf = (docs, id) => {
40
+ const byId = new Map(docs.map((d) => [d.id, d]));
41
+ const out = [];
42
+ const seen = new Set([id]);
43
+ for (let p = byId.get(id)?.parent; p != null && !seen.has(p); p = byId.get(p)?.parent) {
44
+ seen.add(p);
45
+ const d = byId.get(p);
46
+ if (!d) {
47
+ break;
48
+ }
49
+ out.unshift(d);
50
+ }
51
+ return out;
52
+ };
53
+ /** DOM anchor id for a heading — namespaced by doc id. */
54
+ export const headingAnchor = (docId, slug) => `${docId}__${slug}`;
55
+ /** Canonical page URL: manifest ancestors form the path; headings use a separate fragment. */
56
+ export const docPath = (docs, id) => `/${[...ancestorsOf(docs, id).map((doc) => doc.id), id].join("/")}`;
57
+ /** Resolve only the complete hierarchy path; no legacy single-id aliases. */
58
+ export const resolveDocPath = (docs, path) => {
59
+ const parts = path.split("/").filter(Boolean);
60
+ const id = parts.at(-1);
61
+ if (!id || !parts.every(isDocId) || !docs.some((doc) => doc.id === id))
62
+ return undefined;
63
+ return docPath(docs, id) === path ? id : undefined;
64
+ };
@@ -0,0 +1,14 @@
1
+ /** Replaces schema DSL comments with whitespace, preserving line numbers and edit offsets. */
2
+ export declare const scanSchemaComments: (source: string) => {
3
+ source: string;
4
+ comments: {
5
+ from: number;
6
+ to: number;
7
+ kind: "line" | "block";
8
+ closed: boolean;
9
+ }[];
10
+ errors: {
11
+ line: number;
12
+ message: string;
13
+ }[];
14
+ };
@@ -0,0 +1,52 @@
1
+ /** Replaces schema DSL comments with whitespace, preserving line numbers and edit offsets. */
2
+ export const scanSchemaComments = (source) => {
3
+ const comments = [];
4
+ const errors = [];
5
+ let quoted = false;
6
+ let line = 0;
7
+ for (let i = 0; i < source.length; i++) {
8
+ const char = source[i];
9
+ if (char === "\n") {
10
+ line++;
11
+ quoted = false;
12
+ continue;
13
+ }
14
+ if (quoted) {
15
+ if (char === "\\" && source[i + 1] !== "\n")
16
+ i++;
17
+ else if (char === '"')
18
+ quoted = false;
19
+ continue;
20
+ }
21
+ if (char === '"') {
22
+ quoted = true;
23
+ continue;
24
+ }
25
+ if (char !== "/")
26
+ continue;
27
+ if (source[i + 1] === "/") {
28
+ const end = source.indexOf("\n", i + 2);
29
+ const to = end < 0 ? source.length : end;
30
+ comments.push({ from: i, to, kind: "line", closed: end >= 0 });
31
+ i = to - 1;
32
+ }
33
+ else if (source[i + 1] === "*") {
34
+ const end = source.indexOf("*/", i + 2);
35
+ const to = end < 0 ? source.length : end + 2;
36
+ comments.push({ from: i, to, kind: "block", closed: end >= 0 });
37
+ if (end < 0)
38
+ errors.push({ line, message: "/* comment is not closed — */ is required" });
39
+ line += source.slice(i, to).split("\n").length - 1;
40
+ i = to - 1;
41
+ }
42
+ }
43
+ const chunks = [];
44
+ let from = 0;
45
+ for (const comment of comments) {
46
+ chunks.push(source.slice(from, comment.from));
47
+ chunks.push(source.slice(comment.from, comment.to).replace(/[^\r\n]/g, " "));
48
+ from = comment.to;
49
+ }
50
+ chunks.push(source.slice(from));
51
+ return { source: chunks.join(""), comments, errors };
52
+ };
@@ -0,0 +1,9 @@
1
+ import { type JSX } from "react";
2
+ import { type SchemaGraphClientData } from "./schema-graph.js";
3
+ export interface SchemaGraphFlowProps {
4
+ data: SchemaGraphClientData;
5
+ /** Height in normal view (default = sized to content, max 62vh). */
6
+ height?: number | string;
7
+ }
8
+ /** Interactive UI for the schema graph (React Flow). Pass the static SVG's payload as-is. */
9
+ export declare const SchemaGraphFlow: (props: SchemaGraphFlowProps) => JSX.Element;