@plebeiantech/lat.md 0.12.3-fork.15 → 0.12.3-fork.17

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 (182) hide show
  1. package/README.md +1 -1
  2. package/dist/src/cli/checklist-menu.d.ts +2 -1
  3. package/dist/src/cli/checklist-menu.js +3 -2
  4. package/dist/src/cli/index.js +59 -16
  5. package/dist/src/cli/init-preferences.d.ts +4 -0
  6. package/dist/src/cli/init-preferences.js +35 -0
  7. package/dist/src/cli/init.js +10 -5
  8. package/dist/src/cli/reindex.js +4 -8
  9. package/dist/src/cli/search.d.ts +3 -2
  10. package/dist/src/cli/search.js +71 -66
  11. package/dist/src/cli/ui-build-server.d.ts +12 -0
  12. package/dist/src/cli/ui-build-server.js +26 -0
  13. package/dist/src/cli/ui-build.js +1 -1
  14. package/dist/src/cli/ui.js +1 -1
  15. package/dist/src/format.d.ts +5 -0
  16. package/dist/src/format.js +32 -4
  17. package/dist/src/lattice-model.d.ts +8 -1
  18. package/dist/src/lattice-model.js +4 -1
  19. package/dist/src/lattice.d.ts +2 -2
  20. package/dist/src/lattice.js +17 -34
  21. package/dist/src/markdown-analysis.d.ts +11 -0
  22. package/dist/src/markdown-analysis.js +31 -1
  23. package/dist/src/mcp/server.js +6 -6
  24. package/dist/src/parser-cache.d.ts +1 -1
  25. package/dist/src/parser-cache.js +1 -1
  26. package/dist/src/search/cache.d.ts +3 -0
  27. package/dist/src/search/cache.js +71 -0
  28. package/dist/src/search/chunks.d.ts +23 -0
  29. package/dist/src/search/chunks.js +197 -0
  30. package/dist/src/search/db.d.ts +27 -16
  31. package/dist/src/search/db.js +108 -38
  32. package/dist/src/search/index.d.ts +5 -2
  33. package/dist/src/search/index.js +173 -69
  34. package/dist/src/search/lexical.d.ts +6 -0
  35. package/dist/src/search/lexical.js +36 -0
  36. package/dist/src/search/lock.d.ts +2 -0
  37. package/dist/src/search/lock.js +23 -0
  38. package/dist/src/search/query.d.ts +6 -3
  39. package/dist/src/search/query.js +45 -26
  40. package/dist/src/search/search.d.ts +13 -8
  41. package/dist/src/search/search.js +155 -23
  42. package/dist/src/search/types.d.ts +31 -0
  43. package/dist/src/search/types.js +1 -0
  44. package/dist/src/source-formats.d.ts +1 -1
  45. package/dist/src/source-formats.js +4 -0
  46. package/dist/src/source-parser.js +397 -1
  47. package/dist/src/view/client/assets/GraphView-BWlRdQM4.js +456 -0
  48. package/dist/src/view/client/assets/{MarkdownEditor-B7quqPb8.js → MarkdownEditor-MW-Ck1O0.js} +8 -8
  49. package/dist/src/view/client/assets/_commonjsHelpers-CqkleIqs.js +1 -0
  50. package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-Bn3RshMV.js +1 -0
  51. package/dist/src/view/client/assets/{arc-BnwR8Bu7.js → arc-En9tdFQF.js} +1 -1
  52. package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-B-9J1VAM.js +36 -0
  53. package/dist/src/view/client/assets/{blockDiagram-I7D4REHJ-DETl8e_H.js → blockDiagram-I7D4REHJ-QlznwalP.js} +2 -2
  54. package/dist/src/view/client/assets/{c4Diagram-7LVT6UL2-DJRAT1Ve.js → c4Diagram-7LVT6UL2-Bp2y8B4_.js} +2 -2
  55. package/dist/src/view/client/assets/channel-uy0fFvcW.js +1 -0
  56. package/dist/src/view/client/assets/{chunk-2Q5K7J3B-C0TmOC1Q.js → chunk-2Q5K7J3B-CnBKL-lm.js} +1 -1
  57. package/dist/src/view/client/assets/{chunk-5VM5RSS4-dfBOrYE6.js → chunk-5VM5RSS4-LLYMi_n6.js} +1 -1
  58. package/dist/src/view/client/assets/{chunk-F27PBJKO-DAnks99K.js → chunk-F27PBJKO-VIeO7VF0.js} +1 -1
  59. package/dist/src/view/client/assets/{chunk-IMKFNOWR-CPjB07if.js → chunk-IMKFNOWR-D5i67DVD.js} +1 -1
  60. package/dist/src/view/client/assets/{chunk-JWPE2WC7-VvU_gZI6.js → chunk-JWPE2WC7-D4qAtoL3.js} +1 -1
  61. package/dist/src/view/client/assets/{chunk-POPQ4Y6H-DDUbVF55.js → chunk-POPQ4Y6H-BMzOyiW_.js} +1 -1
  62. package/dist/src/view/client/assets/{chunk-SVP7TREG-BHROkr6D.js → chunk-SVP7TREG-DWy5nbfp.js} +1 -1
  63. package/dist/src/view/client/assets/{chunk-TICWLB2K-95PzGIOg.js → chunk-TICWLB2K-CVqCANFB.js} +1 -1
  64. package/dist/src/view/client/assets/{chunk-XXDRQBXY-MdmuLhIs.js → chunk-XXDRQBXY-Bp23h6Tf.js} +1 -1
  65. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-DbSEqjA5.js +1 -0
  66. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-DbSEqjA5.js +1 -0
  67. package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-BCw7fCC2.js +1 -0
  68. package/dist/src/view/client/assets/{cynefin-OW5HDTMX-CeA8IKWo.js → cynefin-OW5HDTMX--lYN4N9m.js} +1 -1
  69. package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-9sK5EV7v.js +62 -0
  70. package/dist/src/view/client/assets/{dagre-GXQ25YYZ-DLQwTa3H.js → dagre-GXQ25YYZ-CoMdEFCq.js} +2 -2
  71. package/dist/src/view/client/assets/{diagram-S7CK7UJ4-kbKdIyXG.js → diagram-S7CK7UJ4-D13dyWdd.js} +2 -2
  72. package/dist/src/view/client/assets/diagram-UQ7AKVKN-PdwLPPPA.js +41 -0
  73. package/dist/src/view/client/assets/{diagram-VSXAHHWV-EXJTU8qq.js → diagram-VSXAHHWV-CtWWa_A7.js} +2 -2
  74. package/dist/src/view/client/assets/diagram-VX7I27RA-BeCYcSU-.js +24 -0
  75. package/dist/src/view/client/assets/diagram-Z3DM3KII-DiWabOBM.js +24 -0
  76. package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-CZQ3MD_K.js +1 -0
  77. package/dist/src/view/client/assets/{erDiagram-RLTQ6QDP-C925J5A3.js → erDiagram-RLTQ6QDP-B1AhXIrP.js} +2 -2
  78. package/dist/src/view/client/assets/{flowDiagram-HODETNUW-DzoCa-Nq.js → flowDiagram-HODETNUW-Bhr0JM6P.js} +1 -1
  79. package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-CbP8iAkg.js +292 -0
  80. package/dist/src/view/client/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  81. package/dist/src/view/client/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  82. package/dist/src/view/client/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  83. package/dist/src/view/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  84. package/dist/src/view/client/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  85. package/dist/src/view/client/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  86. package/dist/src/view/client/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  87. package/dist/src/view/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  88. package/dist/src/view/client/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  89. package/dist/src/view/client/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  90. package/dist/src/view/client/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  91. package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B9uuGdLF.js +106 -0
  92. package/dist/src/view/client/assets/index-DG0Ez15D.css +1 -0
  93. package/dist/src/view/client/assets/index-nBmbYfun.js +13 -0
  94. package/dist/src/view/client/assets/infoDiagram-27XIBGKW-BrfwQ_Cq.js +2 -0
  95. package/dist/src/view/client/assets/{ishikawaDiagram-5VMMS53U-CW4mto2d.js → ishikawaDiagram-5VMMS53U-CHtJ36re.js} +2 -2
  96. package/dist/src/view/client/assets/{journeyDiagram-3NMN7TZE-8R4eacFW.js → journeyDiagram-3NMN7TZE-BChcgX1s.js} +2 -2
  97. package/dist/src/view/client/assets/{kanban-definition-UXKFOSKX-Cy9ioXEJ.js → kanban-definition-UXKFOSKX-BnwFsKiB.js} +6 -6
  98. package/dist/src/view/client/assets/{layout-C0MFuhth.js → layout-Dnd0pLdb.js} +1 -1
  99. package/dist/src/view/client/assets/{linear-Dv3N4dQR.js → linear-CFhW2ozn.js} +1 -1
  100. package/dist/src/view/client/assets/{maplibre-gl-DnqZgfUS.js → maplibre-gl-CDX6Zuow.js} +1 -1
  101. package/dist/src/view/client/assets/{mermaid.core-bds9A7e2.js → mermaid.core--OP6lXtW.js} +11 -11
  102. package/dist/src/view/client/assets/{mindmap-definition-YA3MSWOX-DjmGW355.js → mindmap-definition-YA3MSWOX-D51y2zbm.js} +2 -2
  103. package/dist/src/view/client/assets/pegDiagram-XKGWAZYB--zyQJDvI.js +1 -0
  104. package/dist/src/view/client/assets/{pieDiagram-E7YTZNPT-CCH2gGR-.js → pieDiagram-E7YTZNPT-Cj8mvpJy.js} +2 -2
  105. package/dist/src/view/client/assets/{quadrantDiagram-AXDQQJYC-CjocPJFz.js → quadrantDiagram-AXDQQJYC-qvhJrnBf.js} +4 -4
  106. package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-CtWvxpSo.js +1 -0
  107. package/dist/src/view/client/assets/{requirementDiagram-BXWQKSXE-Ct6AoXRr.js → requirementDiagram-BXWQKSXE-CBxen_KG.js} +2 -2
  108. package/dist/src/view/client/assets/{sankeyDiagram-P5KCCOFB-CIpgGN_w.js → sankeyDiagram-P5KCCOFB-B_fUkmfc.js} +2 -2
  109. package/dist/src/view/client/assets/{sequenceDiagram-WJ2MYXX4-DrmLFj6w.js → sequenceDiagram-WJ2MYXX4-Dclv4p68.js} +2 -2
  110. package/dist/src/view/client/assets/sizeCapture-INFHLROL-GfG0sMTZ.js +1 -0
  111. package/dist/src/view/client/assets/stateDiagram-D77RDMKH-YQWPnzxU.js +1 -0
  112. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-gUj6Hzgl.js +1 -0
  113. package/dist/src/view/client/assets/swimlanes-42K2YHIH-5yiCveVS.js +1 -0
  114. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-DR7JhrxL.js +8 -0
  115. package/dist/src/view/client/assets/{timeline-definition-24CTP7MA-BExPu6X5.js → timeline-definition-24CTP7MA-BvAbB5Zs.js} +2 -2
  116. package/dist/src/view/client/assets/{vennDiagram-4TSXK5OY-PC1MU3si.js → vennDiagram-4TSXK5OY-guC-r60n.js} +2 -2
  117. package/dist/src/view/client/assets/{wardleyDiagram-VM6X3IG4-DNWqU3fa.js → wardleyDiagram-VM6X3IG4-C0yXcU2x.js} +2 -2
  118. package/dist/src/view/client/assets/{xychartDiagram-S5SC5T6Z-E1uA28_D.js → xychartDiagram-S5SC5T6Z-RPmkPNfK.js} +2 -2
  119. package/dist/src/view/client/index.html +2 -2
  120. package/dist/src/view/document-route.d.ts +2 -0
  121. package/dist/src/view/document-route.js +40 -7
  122. package/dist/src/view/graph.d.ts +2 -2
  123. package/dist/src/view/graph.js +7 -3
  124. package/dist/src/view/highlight.js +207 -203
  125. package/dist/src/view/markdown.js +19 -0
  126. package/dist/src/view/preindexed-search.d.ts +15 -0
  127. package/dist/src/view/preindexed-search.js +25 -0
  128. package/dist/src/view/protocol.d.ts +10 -1
  129. package/dist/src/view/references.js +6 -7
  130. package/dist/src/view/repository.js +10 -2
  131. package/dist/src/view/search-result.d.ts +4 -0
  132. package/dist/src/view/search-result.js +25 -0
  133. package/dist/src/view/search.d.ts +7 -2
  134. package/dist/src/view/search.js +23 -30
  135. package/dist/src/view/server-build.d.ts +21 -0
  136. package/dist/src/view/server-build.js +145 -0
  137. package/dist/src/view/server-deployment.d.ts +32 -0
  138. package/dist/src/view/server-deployment.js +134 -0
  139. package/dist/src/view/server-index-worker.d.ts +6 -0
  140. package/dist/src/view/server-index-worker.js +14 -0
  141. package/dist/src/view/server.js +25 -7
  142. package/dist/src/view/source-target.d.ts +2 -0
  143. package/dist/src/view/source-target.js +24 -0
  144. package/dist/src/view/static-build.d.ts +16 -1
  145. package/dist/src/view/static-build.js +187 -89
  146. package/dist/src/view/static-protocol.d.ts +7 -0
  147. package/dist/src/view/static-protocol.js +5 -0
  148. package/dist/src/view/store.js +17 -7
  149. package/dist/src/view/vercel-build.d.ts +24 -0
  150. package/dist/src/view/vercel-build.js +140 -0
  151. package/dist/src/view/vercel-server-build.d.ts +10 -0
  152. package/dist/src/view/vercel-server-build.js +61 -0
  153. package/dist/vendor/lat.md-server/index.d.ts +1 -1
  154. package/dist/vendor/lat.md-server/index.js +1 -1
  155. package/package.json +20 -5
  156. package/templates/AGENTS.md +1 -1
  157. package/templates/cursor-rules.md +1 -1
  158. package/templates/skill/SKILL.md +1 -1
  159. package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-V7-T6qsl.js +0 -1
  160. package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-u97vDna9.js +0 -36
  161. package/dist/src/view/client/assets/channel-BwQbnWCN.js +0 -1
  162. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-HvQKkIS-.js +0 -1
  163. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-HvQKkIS-.js +0 -1
  164. package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-CauUf_rN.js +0 -1
  165. package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-L1rtcT8l.js +0 -62
  166. package/dist/src/view/client/assets/diagram-UQ7AKVKN-kmzp92RC.js +0 -41
  167. package/dist/src/view/client/assets/diagram-VX7I27RA-DAiIyMCG.js +0 -24
  168. package/dist/src/view/client/assets/diagram-Z3DM3KII-G9wz50ZV.js +0 -24
  169. package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-D2tVRCNA.js +0 -1
  170. package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-D2ebbg4p.js +0 -292
  171. package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B4QratYy.js +0 -106
  172. package/dist/src/view/client/assets/index-D0ajr_Ja.css +0 -1
  173. package/dist/src/view/client/assets/index-KZcnpXJH.js +0 -321
  174. package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DL4mtHkt.js +0 -2
  175. package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-BWsZp0ab.js +0 -1
  176. package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BjLeb2KF.js +0 -1
  177. package/dist/src/view/client/assets/sizeCapture-INFHLROL-BdFMe2Lr.js +0 -1
  178. package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Zd9F1goW.js +0 -1
  179. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-ByIKCftl.js +0 -1
  180. package/dist/src/view/client/assets/swimlanes-42K2YHIH-a7VFwi00.js +0 -1
  181. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-CzxaUHB-.js +0 -8
  182. /package/dist/src/view/client/assets/{logo-small-RR_KBOAT.svg → logo-RR_KBOAT.svg} +0 -0
@@ -18,6 +18,7 @@ const CODE_LINK_CLASSES = [
18
18
  'code-language-ts',
19
19
  'code-language-js',
20
20
  'code-language-py',
21
+ 'code-language-rb',
21
22
  'code-language-rs',
22
23
  'code-language-go',
23
24
  'code-language-c',
@@ -323,6 +324,10 @@ function codeLanguage(target) {
323
324
  return { className: 'code-language-js', label: 'JS' };
324
325
  case '.py':
325
326
  return { className: 'code-language-py', label: 'PY' };
327
+ case '.erb':
328
+ case '.rake':
329
+ case '.rb':
330
+ return { className: 'code-language-rb', label: 'RB' };
326
331
  case '.rs':
327
332
  return { className: 'code-language-rs', label: 'RS' };
328
333
  case '.go':
@@ -488,6 +493,11 @@ export async function renderMarkdown(markdown, filePath, resolveWikiLink, option
488
493
  node.url = options.rewriteMarkdownLink(node.url);
489
494
  }
490
495
  });
496
+ visit(tree, 'definition', (node) => {
497
+ if (options.rewriteMarkdownLink) {
498
+ node.url = options.rewriteMarkdownLink(node.url);
499
+ }
500
+ });
491
501
  const firstHeading = tree.children.find((node) => node.type === 'heading');
492
502
  const title = firstHeading
493
503
  ? nodeText(firstHeading)
@@ -563,6 +573,15 @@ export async function renderMarkdown(markdown, filePath, resolveWikiLink, option
563
573
  };
564
574
  });
565
575
  const hast = await documentTreeProcessor.run(tree);
576
+ // Attach source ranges after sanitization, before positions are discarded.
577
+ visit(hast, 'element', (node) => {
578
+ if (!/^(p|pre|li|tr|h[1-6])$/.test(node.tagName) || !node.position)
579
+ return;
580
+ node.properties['data-source-start-line'] =
581
+ node.position.start.line + (options.lineOffset ?? 0);
582
+ node.properties['data-source-end-line'] =
583
+ node.position.end.line + (options.lineOffset ?? 0);
584
+ });
566
585
  return {
567
586
  tree: decorateExternalSiteLinks(toViewDocumentTree(hast)),
568
587
  title,
@@ -0,0 +1,15 @@
1
+ import type { Section } from '../lattice-model.js';
2
+ import type { CreateSearchEngine } from '../search/embedder.js';
3
+ import { type IndexedSearchSession } from '../search/query.js';
4
+ import type { ViewSearchResponse } from './protocol.js';
5
+ export type PreindexedViewSearchDependencies = {
6
+ openSearchSession: (latDir: string, options: {
7
+ cacheDir?: string;
8
+ createSearchEngine?: CreateSearchEngine;
9
+ }) => Promise<IndexedSearchSession>;
10
+ };
11
+ export type PreindexedViewSearch = ((query: string) => Promise<ViewSearchResponse>) & {
12
+ close: () => Promise<void>;
13
+ };
14
+ /** Serve a built search index without importing indexing or Markdown parsers. */
15
+ export declare function createPreindexedViewSearch(latDir: string, cacheDir: string, sections: readonly Section[], documentPaths: ReadonlyMap<string, string>, dependencies?: PreindexedViewSearchDependencies, createSearchEngine?: CreateSearchEngine): Promise<PreindexedViewSearch>;
@@ -0,0 +1,25 @@
1
+ import { openIndexedSearchSession, resolveSearchMatches, } from '../search/query.js';
2
+ import { viewSearchResult } from './search-result.js';
3
+ const VIEW_SEARCH_LIMIT = 10;
4
+ /** Serve a built search index without importing indexing or Markdown parsers. */
5
+ export async function createPreindexedViewSearch(latDir, cacheDir, sections, documentPaths, dependencies = {
6
+ openSearchSession: openIndexedSearchSession,
7
+ }, createSearchEngine) {
8
+ const sectionById = new Map(sections.map((section) => [section.id.toLowerCase(), section]));
9
+ const session = await dependencies.openSearchSession(latDir, {
10
+ cacheDir,
11
+ createSearchEngine,
12
+ });
13
+ const search = async (rawQuery) => {
14
+ const query = rawQuery.trim();
15
+ if (!query)
16
+ return { query: '', results: [] };
17
+ const results = await session.search(query, VIEW_SEARCH_LIMIT);
18
+ return {
19
+ query,
20
+ results: resolveSearchMatches(results, sectionById).map((match) => viewSearchResult(latDir, match, documentPaths)),
21
+ };
22
+ };
23
+ search.close = session.close;
24
+ return search;
25
+ }
@@ -1,3 +1,4 @@
1
+ import type { SearchEvidence } from '../search/types.js';
1
2
  export type ViewGitFileStatus = 'modified' | 'new';
2
3
  export type ViewGitState = {
3
4
  files: Record<string, ViewGitFileStatus>;
@@ -9,6 +10,8 @@ export type ViewExternalFile = {
9
10
  };
10
11
  export type ViewIndex = {
11
12
  files: string[];
13
+ /** Authored directory-index entries, keyed by vault-relative directory ('' at root). */
14
+ directoryOrder: Record<string, string[]>;
12
15
  externalFiles: ViewExternalFile[];
13
16
  entry: string;
14
17
  errorCounts: Record<string, number>;
@@ -30,7 +33,12 @@ export type ViewSearchResult = {
30
33
  breadcrumbs: string[];
31
34
  description: string;
32
35
  url: string;
33
- score: number;
36
+ rankScore: number;
37
+ introduction: string;
38
+ evidence: SearchEvidence[];
39
+ semanticSimilarity?: number;
40
+ semanticRank?: number;
41
+ lexicalRank?: number;
34
42
  };
35
43
  export type ViewSearchResponse = {
36
44
  query: string;
@@ -73,6 +81,7 @@ export type ViewGraphNode = {
73
81
  label: string;
74
82
  url: string;
75
83
  breadcrumbs: string[];
84
+ /** Local documents sum direct backlink counts across every heading. Other nodes use incoming edge weights. */
76
85
  inDegree: number;
77
86
  outDegree: number;
78
87
  documentPath?: string;
@@ -3,14 +3,14 @@ import { buildFileIndex, buildSectionSlugIndex, flattenSections, resolveRef, } f
3
3
  import { analyzeMarkdownFile, } from '../markdown-analysis.js';
4
4
  import { toPosix } from '../path.js';
5
5
  import { renderMarkdown } from './markdown.js';
6
- import { viewSourceTarget } from './source-target.js';
7
- import { documentUrl as routeDocumentUrl, rewriteDocumentLink, } from './document-route.js';
6
+ import { viewSourceTarget, rewriteLocalFileLink } from './source-target.js';
7
+ import { documentUrl as routeDocumentUrl } from './document-route.js';
8
8
  /** Parse one Markdown file once into every structure needed by the view store. */
9
9
  export function parseViewMarkdownFile(absolutePath, content, latDir, projectRoot) {
10
10
  return analyzeMarkdownFile(absolutePath, content, latDir, projectRoot);
11
11
  }
12
12
  function contextMarkdownLink(requestedPath, url) {
13
- return rewriteDocumentLink(url, requestedPath);
13
+ return rewriteLocalFileLink(url, requestedPath);
14
14
  }
15
15
  function documentUrl(latDir, projectRoot, section) {
16
16
  const path = toPosix(relative(latDir, resolve(projectRoot, section.filePath)));
@@ -34,20 +34,19 @@ export function linkedSection(url, sourcePath, sectionsByPath) {
34
34
  .join('/');
35
35
  let destination;
36
36
  try {
37
- destination = new URL(url, `http://lat.local/docs/${encodedSourcePath}`);
37
+ destination = new URL(url, `http://lat.local/${encodedSourcePath}`);
38
38
  }
39
39
  catch {
40
40
  return null;
41
41
  }
42
- if (destination.origin !== 'http://lat.local' ||
43
- !destination.pathname.startsWith('/docs/')) {
42
+ if (destination.origin !== 'http://lat.local') {
44
43
  return null;
45
44
  }
46
45
  let path;
47
46
  let fragment;
48
47
  try {
49
48
  path = destination.pathname
50
- .slice('/docs/'.length)
49
+ .slice(1)
51
50
  .split('/')
52
51
  .map(decodeURIComponent)
53
52
  .join('/');
@@ -316,7 +316,7 @@ async function readViewSource(latDir, projectRoot, requestedPath, requestedSymbo
316
316
  if (!requestedPath ||
317
317
  requestedPath.includes('\\') ||
318
318
  isAbsolute(requestedPath) ||
319
- !isSourceFileExtension(extname(requestedPath))) {
319
+ (requestedSymbol && !isSourceFileExtension(extname(requestedPath)))) {
320
320
  throw new ViewSourceNotFoundError('Source document not found');
321
321
  }
322
322
  const candidate = resolve(projectRoot, requestedPath);
@@ -334,7 +334,15 @@ async function readViewSource(latDir, projectRoot, requestedPath, requestedSymbo
334
334
  if (!isInside(realRoot, realFile)) {
335
335
  throw new ViewSourceNotFoundError('Source document not found');
336
336
  }
337
- const content = await readFile(realFile, 'utf-8');
337
+ let content;
338
+ try {
339
+ content = new TextDecoder('utf-8', { fatal: true }).decode(await readFile(realFile));
340
+ if (content.includes('\0'))
341
+ throw new Error('Binary file');
342
+ }
343
+ catch {
344
+ throw new ViewSourceNotFoundError('Source file is not readable UTF-8 text');
345
+ }
338
346
  if (!requestedSymbol) {
339
347
  if (!requestedLine)
340
348
  return { path: requestedPath, content, focus: null };
@@ -0,0 +1,4 @@
1
+ import type { SectionMatch } from '../lattice-model.js';
2
+ import type { ViewSearchResult } from './protocol.js';
3
+ /** Convert one graph section match into the browser search protocol. */
4
+ export declare function viewSearchResult(latDir: string, match: SectionMatch, documentPaths?: ReadonlyMap<string, string>): ViewSearchResult;
@@ -0,0 +1,25 @@
1
+ import { dirname, relative, resolve } from 'node:path';
2
+ import { toPosix } from '../path.js';
3
+ import { documentUrl } from './document-route.js';
4
+ /** Convert one graph section match into the browser search protocol. */
5
+ export function viewSearchResult(latDir, match, documentPaths) {
6
+ const section = match.section;
7
+ const projectRoot = dirname(latDir);
8
+ const path = documentPaths?.get(section.id.toLowerCase()) ??
9
+ toPosix(relative(latDir, resolve(projectRoot, section.filePath)));
10
+ const fileBreadcrumbs = path.replace(/\.md$/i, '').split('/');
11
+ return {
12
+ sectionId: section.id,
13
+ title: section.heading,
14
+ path,
15
+ breadcrumbs: [...fileBreadcrumbs, ...section.id.split('#').slice(1)],
16
+ description: match.evidence?.[0]?.text ?? section.firstParagraph,
17
+ introduction: section.firstParagraph,
18
+ evidence: match.evidence ?? [],
19
+ semanticSimilarity: match.semanticSimilarity,
20
+ semanticRank: match.semanticRank,
21
+ lexicalRank: match.lexicalRank,
22
+ url: documentUrl(path, section.githubSlug ?? ''),
23
+ rankScore: match.rankScore ?? 0,
24
+ };
25
+ }
@@ -1,4 +1,4 @@
1
- import type { SectionMatch } from '../lattice-model.js';
1
+ import type { Section, SectionMatch } from '../lattice-model.js';
2
2
  import type { ViewSearchResponse } from './protocol.js';
3
3
  export type ViewSearch = (query: string) => Promise<ViewSearchResponse>;
4
4
  export type ViewSearchDependencies = {
@@ -11,4 +11,9 @@ export type ViewSearchDependencies = {
11
11
  }>;
12
12
  };
13
13
  /** Create the lazily indexed semantic search service used by `lat ui`. */
14
- export declare function createViewSearch(latDir: string, dependencies?: ViewSearchDependencies, getGeneration?: () => number): ViewSearch;
14
+ export declare function createViewSearch(latDir: string, dependencies?: ViewSearchDependencies, getGeneration?: () => number, options?: {
15
+ cacheDir?: string;
16
+ preindexed?: boolean;
17
+ sections?: readonly Section[];
18
+ documentPaths?: ReadonlyMap<string, string>;
19
+ }): ViewSearch;
@@ -1,41 +1,34 @@
1
- import { dirname, relative, resolve } from 'node:path';
2
- import { toPosix } from '../path.js';
3
- import { documentUrl } from './document-route.js';
1
+ import { viewSearchResult } from './search-result.js';
4
2
  const VIEW_SEARCH_LIMIT = 10;
5
- const defaultDependencies = {
6
- async runIndex(latDir) {
7
- const { runIndex } = await import('../cli/search.js');
8
- await runIndex(latDir);
9
- },
10
- async runSearch(latDir, query, limit, options) {
11
- const { runSearch } = await import('../cli/search.js');
12
- return runSearch(latDir, query, limit, undefined, options);
13
- },
14
- };
15
- function viewSearchResult(latDir, match) {
16
- const section = match.section;
17
- const projectRoot = dirname(latDir);
18
- const path = toPosix(relative(latDir, resolve(projectRoot, section.filePath)));
19
- const fileBreadcrumbs = path.replace(/\.md$/i, '').split('/');
3
+ function defaultDependencies(cacheDir, sectionById) {
20
4
  return {
21
- sectionId: section.id,
22
- title: section.heading,
23
- path,
24
- breadcrumbs: [...fileBreadcrumbs, ...section.id.split('#').slice(1)],
25
- description: section.firstParagraph,
26
- url: documentUrl(path, section.githubSlug ?? ''),
27
- score: match.score ?? 0,
5
+ async runIndex(latDir) {
6
+ const { runIndex } = await import('../cli/search.js');
7
+ await runIndex(latDir, undefined, undefined, { cacheDir });
8
+ },
9
+ async runSearch(latDir, query, limit, options) {
10
+ const { runSearch } = await import('../cli/search.js');
11
+ return runSearch(latDir, query, limit, undefined, {
12
+ ...options,
13
+ cacheDir,
14
+ sectionById,
15
+ });
16
+ },
28
17
  };
29
18
  }
30
19
  /** Create the lazily indexed semantic search service used by `lat ui`. */
31
- export function createViewSearch(latDir, dependencies = defaultDependencies, getGeneration = () => 0) {
20
+ export function createViewSearch(latDir, dependencies, getGeneration = () => 0, options = {}) {
21
+ const sectionById = options.sections
22
+ ? new Map(options.sections.map((section) => [section.id.toLowerCase(), section]))
23
+ : undefined;
24
+ const resolvedDependencies = dependencies ?? defaultDependencies(options.cacheDir, sectionById);
32
25
  let indexReady = null;
33
- let indexedGeneration = -1;
26
+ let indexedGeneration = options.preindexed ? getGeneration() : -1;
34
27
  const prepareIndex = async () => {
35
28
  while (indexedGeneration < getGeneration() || indexedGeneration < 0) {
36
29
  if (!indexReady) {
37
30
  const generation = getGeneration();
38
- indexReady = dependencies
31
+ indexReady = resolvedDependencies
39
32
  .runIndex(latDir)
40
33
  .then(() => {
41
34
  indexedGeneration = Math.max(indexedGeneration, generation);
@@ -52,10 +45,10 @@ export function createViewSearch(latDir, dependencies = defaultDependencies, get
52
45
  if (!query)
53
46
  return { query: '', results: [] };
54
47
  await prepareIndex();
55
- const search = await dependencies.runSearch(latDir, query, VIEW_SEARCH_LIMIT, { buildIndex: false });
48
+ const search = await resolvedDependencies.runSearch(latDir, query, VIEW_SEARCH_LIMIT, { buildIndex: false });
56
49
  return {
57
50
  query: search.query,
58
- results: search.matches.map((match) => viewSearchResult(latDir, match)),
51
+ results: search.matches.map((match) => viewSearchResult(latDir, match, options.documentPaths)),
59
52
  };
60
53
  };
61
54
  }
@@ -0,0 +1,21 @@
1
+ import { getLatServerVersion } from '../../vendor/lat.md-server/index.js';
2
+ import type { CmdContext } from '../context.js';
3
+ import { analyzeMarkdownProject, type MarkdownProjectAnalysis } from '../project-analysis.js';
4
+ import { getLocalVersion } from '../version.js';
5
+ import { buildStaticView, type StaticViewBuildOptions, type StaticViewBuildResult } from './static-build.js';
6
+ export type ServerViewBuildOptions = Omit<StaticViewBuildOptions, 'searchApi'>;
7
+ export type ServerViewBuildResult = StaticViewBuildResult;
8
+ export type ServerViewBuildDependencies = {
9
+ analyzeMarkdownProject: typeof analyzeMarkdownProject;
10
+ buildStaticView: typeof buildStaticView;
11
+ getLocalVersion: typeof getLocalVersion;
12
+ getLatServerVersion: typeof getLatServerVersion;
13
+ getPackageVersion: typeof getPackageVersion;
14
+ buildSearchIndex: typeof buildServerSearchIndex;
15
+ };
16
+ /** Wait for the index process to exit so native handles cannot lock staging. */
17
+ export declare function buildServerSearchIndex(latDir: string, project: MarkdownProjectAnalysis, cacheDir: string): Promise<void>;
18
+ declare function getPackageVersion(name: string): string;
19
+ /** Build immutable UI data plus the small portable Express search service. */
20
+ export declare function buildServerView(ctx: CmdContext, requestedOutput: string, options?: ServerViewBuildOptions, dependencies?: ServerViewBuildDependencies): Promise<ServerViewBuildResult>;
21
+ export {};
@@ -0,0 +1,145 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fork } from 'node:child_process';
3
+ import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { dirname, join, relative, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { getLatServerVersion } from '../../vendor/lat.md-server/index.js';
7
+ import { analyzeMarkdownProject, } from '../project-analysis.js';
8
+ import { getLocalVersion } from '../version.js';
9
+ import { toPosix } from '../path.js';
10
+ import { buildStaticView, moveViewBuildOutput, removeViewBuildStaging, normalizeStaticViewBasePath, validateViewBuildOutput, } from './static-build.js';
11
+ import { SERVER_VIEW_SCHEMA_VERSION, } from './server-deployment.js';
12
+ const defaultDependencies = {
13
+ analyzeMarkdownProject,
14
+ buildStaticView,
15
+ getLocalVersion,
16
+ getLatServerVersion,
17
+ getPackageVersion,
18
+ buildSearchIndex: buildServerSearchIndex,
19
+ };
20
+ /** Wait for the index process to exit so native handles cannot lock staging. */
21
+ export function buildServerSearchIndex(latDir, project, cacheDir) {
22
+ const sourceRuntime = import.meta.url.endsWith('.ts');
23
+ const child = fork(new URL(sourceRuntime ? './server-index-worker.ts' : './server-index-worker.js', import.meta.url), [], {
24
+ execArgv: sourceRuntime ? ['--import', 'tsx'] : [],
25
+ serialization: 'advanced',
26
+ stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
27
+ });
28
+ return new Promise((resolveIndex, reject) => {
29
+ let stderr = '';
30
+ let sendError = null;
31
+ child.stderr.setEncoding('utf8');
32
+ child.stderr.on('data', (chunk) => {
33
+ stderr = (stderr + chunk).slice(-64_000);
34
+ });
35
+ child.once('error', reject);
36
+ child.once('close', (code, signal) => {
37
+ if (sendError)
38
+ reject(sendError);
39
+ else if (code === 0)
40
+ resolveIndex();
41
+ else {
42
+ reject(new Error(`Server search indexing failed (${signal ?? code}): ${stderr.trim()}`));
43
+ }
44
+ });
45
+ child.send({ latDir, project, cacheDir }, (error) => {
46
+ if (error) {
47
+ sendError = error;
48
+ child.kill();
49
+ }
50
+ });
51
+ });
52
+ }
53
+ function getPackageVersion(name) {
54
+ let directory = dirname(fileURLToPath(import.meta.resolve(name)));
55
+ while (true) {
56
+ try {
57
+ const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
58
+ if (manifest.name === name && manifest.version)
59
+ return manifest.version;
60
+ }
61
+ catch (error) {
62
+ if (error.code !== 'ENOENT')
63
+ throw error;
64
+ }
65
+ const parent = dirname(directory);
66
+ if (parent === directory)
67
+ break;
68
+ directory = parent;
69
+ }
70
+ throw new Error(`Could not resolve package version: ${name}`);
71
+ }
72
+ function appModule() {
73
+ return `import { createEmbedder } from '@lat.md/embed';
74
+ import minilm from '@lat.md/embed-minilm-fp16';
75
+ import express from 'express';
76
+ import { createServerViewApp } from 'lat.md/server';
77
+ const view = createServerViewApp({
78
+ app: express(),
79
+ manifestFile: new URL('./server-data/server.json', import.meta.url),
80
+ indexFile: new URL('./server-data/search.db', import.meta.url),
81
+ createSearchEngine: () => createEmbedder({ model: minilm }),
82
+ });
83
+ export const close = view.close;
84
+ export default view.app;
85
+ `;
86
+ }
87
+ function packageManifest(latVersion, serverVersion, runtimeVersions) {
88
+ return `${JSON.stringify({
89
+ name: 'lat-ui-server',
90
+ private: true,
91
+ type: 'module',
92
+ scripts: { start: 'lat-ui-server app.mjs' },
93
+ dependencies: {
94
+ '@lat.md/embed': runtimeVersions.embed,
95
+ '@lat.md/embed-minilm-fp16': runtimeVersions.model,
96
+ '../../vendor/lat.md-server/index.js': serverVersion,
97
+ express: runtimeVersions.express,
98
+ 'lat.md': latVersion,
99
+ },
100
+ }, null, 2)}\n`;
101
+ }
102
+ /** Build immutable UI data plus the small portable Express search service. */
103
+ export async function buildServerView(ctx, requestedOutput, options = {}, dependencies = defaultDependencies) {
104
+ const outputDir = resolve(ctx.projectRoot, requestedOutput);
105
+ const basePath = normalizeStaticViewBasePath(options.basePath ?? '/');
106
+ await validateViewBuildOutput(outputDir, ctx.projectRoot, 'Server', options.force);
107
+ await mkdir(dirname(outputDir), { recursive: true });
108
+ const stagingDir = await mkdtemp(join(dirname(outputDir), '.lat-server-staging-'));
109
+ try {
110
+ const publicDir = join(stagingDir, 'public');
111
+ const dataDir = join(stagingDir, 'server-data');
112
+ const staticResult = await dependencies.buildStaticView(ctx, publicDir, {
113
+ ...options,
114
+ basePath,
115
+ searchApi: `${basePath}api/search`,
116
+ });
117
+ const project = await dependencies.analyzeMarkdownProject(ctx.latDir, ctx.projectRoot, { executor: 'auto' });
118
+ await dependencies.buildSearchIndex(ctx.latDir, project, dataDir);
119
+ const sections = project.sections.map(({ children: _children, ...section }) => ({
120
+ ...section,
121
+ documentPath: toPosix(relative(ctx.latDir, resolve(ctx.projectRoot, section.filePath))),
122
+ }));
123
+ const manifest = {
124
+ version: SERVER_VIEW_SCHEMA_VERSION,
125
+ basePath,
126
+ sections,
127
+ };
128
+ await writeFile(join(dataDir, 'server.json'), `${JSON.stringify(manifest)}\n`);
129
+ await writeFile(join(stagingDir, 'app.mjs'), appModule());
130
+ await writeFile(join(stagingDir, 'package.json'), packageManifest(dependencies.getLocalVersion(), dependencies.getLatServerVersion(), {
131
+ embed: dependencies.getPackageVersion('@lat.md/embed'),
132
+ express: dependencies.getPackageVersion('express'),
133
+ model: dependencies.getPackageVersion('@lat.md/embed-minilm-fp16'),
134
+ }));
135
+ if (options.force) {
136
+ await rm(outputDir, { recursive: true, force: true });
137
+ }
138
+ await moveViewBuildOutput(stagingDir, outputDir);
139
+ return { ...staticResult, outputDir };
140
+ }
141
+ catch (error) {
142
+ await removeViewBuildStaging(stagingDir);
143
+ throw error;
144
+ }
145
+ }
@@ -0,0 +1,32 @@
1
+ import { type LatServerApp } from '../../vendor/lat.md-server/index.js';
2
+ import type { Express } from 'express';
3
+ import type { Section } from '../lattice-model.js';
4
+ import type { CreateSearchEngine } from '../search/embedder.js';
5
+ import type { ViewSearchResponse } from './protocol.js';
6
+ export declare const SERVER_VIEW_SCHEMA_VERSION = 1;
7
+ export type ServerViewSection = Omit<Section, 'children'> & {
8
+ documentPath: string;
9
+ };
10
+ export type ServerViewManifest = {
11
+ version: number;
12
+ basePath: string;
13
+ sections: ServerViewSection[];
14
+ };
15
+ export type ServerViewAppOptions = {
16
+ app: Express;
17
+ manifestFile: string | URL;
18
+ indexFile: string | URL;
19
+ cacheDir?: string | URL;
20
+ createSearchEngine?: CreateSearchEngine;
21
+ search?: (query: string) => Promise<ViewSearchResponse>;
22
+ };
23
+ export type ServerViewApp = {
24
+ app: LatServerApp;
25
+ close: () => Promise<void>;
26
+ };
27
+ /**
28
+ * Create the portable Express application emitted by `lat ui build server`.
29
+ * Static assets are also mounted for ordinary Node hosts; platforms with a CDN
30
+ * can serve `public/` directly and forward only the search route to this app.
31
+ */
32
+ export declare function createServerViewApp(options: ServerViewAppOptions): ServerViewApp;
@@ -0,0 +1,134 @@
1
+ import { INDEX_FILE } from '../search/db.js';
2
+ import { copyFile, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createLatServerApp } from '../../vendor/lat.md-server/index.js';
7
+ import { createPreindexedViewSearch, } from './preindexed-search.js';
8
+ export const SERVER_VIEW_SCHEMA_VERSION = 1;
9
+ function searchRoute(basePath) {
10
+ return `${basePath === '/' ? '' : basePath.slice(0, -1)}/api/search`;
11
+ }
12
+ function sendJson(res, status, value, headOnly) {
13
+ const body = JSON.stringify(value);
14
+ res.statusCode = status;
15
+ res.setHeader('Cache-Control', 'no-store');
16
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
17
+ res.setHeader('Content-Length', Buffer.byteLength(body));
18
+ res.end(headOnly ? undefined : body);
19
+ }
20
+ function localPath(path) {
21
+ return resolve(typeof path === 'string' ? path : fileURLToPath(path));
22
+ }
23
+ async function readManifest(path) {
24
+ const manifest = JSON.parse(await readFile(path, 'utf8'));
25
+ if (manifest.version !== SERVER_VIEW_SCHEMA_VERSION ||
26
+ typeof manifest.basePath !== 'string' ||
27
+ !Array.isArray(manifest.sections)) {
28
+ throw new Error(`Unsupported Lat server build manifest: ${path}`);
29
+ }
30
+ return manifest;
31
+ }
32
+ /** Native SQLite handles may outlive close until the Windows process exits. */
33
+ async function removeRuntimeCache(path) {
34
+ try {
35
+ await rm(path, {
36
+ recursive: true,
37
+ force: true,
38
+ maxRetries: 10,
39
+ retryDelay: 150,
40
+ });
41
+ }
42
+ catch (error) {
43
+ const code = error.code;
44
+ if (process.platform !== 'win32' ||
45
+ (code !== 'EBUSY' && code !== 'EPERM' && code !== 'ENOTEMPTY')) {
46
+ throw error;
47
+ }
48
+ // This is an owned OS-temp copy, never the deployed index or user data.
49
+ // A lingering native lock must not fail otherwise successful shutdown.
50
+ }
51
+ }
52
+ async function prepareServerView(options, manifestFile, indexFile) {
53
+ const manifest = await readManifest(manifestFile);
54
+ let runtimeCacheDir = options.cacheDir ? localPath(options.cacheDir) : '';
55
+ let ownsCache = false;
56
+ let search = options.search;
57
+ let ownedSearch;
58
+ if (!search) {
59
+ if (!runtimeCacheDir) {
60
+ runtimeCacheDir = await mkdtemp(join(tmpdir(), 'lat-ui-search-'));
61
+ ownsCache = true;
62
+ }
63
+ await mkdir(runtimeCacheDir, { recursive: true });
64
+ try {
65
+ await copyFile(indexFile, join(runtimeCacheDir, INDEX_FILE));
66
+ }
67
+ catch (error) {
68
+ if (ownsCache) {
69
+ await removeRuntimeCache(runtimeCacheDir);
70
+ }
71
+ throw error;
72
+ }
73
+ const documentPaths = new Map();
74
+ const sections = manifest.sections.map(({ documentPath, ...section }) => {
75
+ documentPaths.set(section.id.toLowerCase(), documentPath);
76
+ return { ...section, children: [] };
77
+ });
78
+ ownedSearch = await createPreindexedViewSearch(dirname(indexFile), runtimeCacheDir, sections, documentPaths, undefined, options.createSearchEngine);
79
+ search = ownedSearch;
80
+ }
81
+ return {
82
+ manifest,
83
+ search,
84
+ close: async () => {
85
+ try {
86
+ await ownedSearch?.close();
87
+ }
88
+ finally {
89
+ if (ownsCache)
90
+ await removeRuntimeCache(runtimeCacheDir);
91
+ }
92
+ },
93
+ };
94
+ }
95
+ /**
96
+ * Create the portable Express application emitted by `lat ui build server`.
97
+ * Static assets are also mounted for ordinary Node hosts; platforms with a CDN
98
+ * can serve `public/` directly and forward only the search route to this app.
99
+ */
100
+ export function createServerViewApp(options) {
101
+ const manifestFile = localPath(options.manifestFile);
102
+ const publicDir = resolve(dirname(manifestFile), '..', 'public');
103
+ const prepared = prepareServerView(options, manifestFile, localPath(options.indexFile));
104
+ const app = createLatServerApp({
105
+ publicDir,
106
+ async handle(req, res, next) {
107
+ const { manifest, search } = await prepared;
108
+ const method = req.method ?? 'GET';
109
+ const headOnly = method === 'HEAD';
110
+ if (method !== 'GET' && !headOnly)
111
+ return next();
112
+ const url = new URL(req.url ?? '/', 'http://lat.local');
113
+ if (url.pathname !== searchRoute(manifest.basePath))
114
+ return next();
115
+ try {
116
+ const query = (url.searchParams.get('query') ?? '').trim();
117
+ if (query.length > 500) {
118
+ sendJson(res, 400, { error: 'Search query is too long' }, headOnly);
119
+ return;
120
+ }
121
+ sendJson(res, 200, await search(query), headOnly);
122
+ }
123
+ catch (error) {
124
+ next(error);
125
+ }
126
+ },
127
+ }, options.app);
128
+ return {
129
+ app,
130
+ close: async () => {
131
+ await (await prepared).close();
132
+ },
133
+ };
134
+ }
@@ -0,0 +1,6 @@
1
+ import type { MarkdownProjectAnalysis } from '../project-analysis.js';
2
+ export type ServerIndexRequest = {
3
+ latDir: string;
4
+ project: MarkdownProjectAnalysis;
5
+ cacheDir: string;
6
+ };