Skip to content

Commit ddf9327

Browse files
committed
feat(md-db): add parser to db
1 parent 8e6babe commit ddf9327

File tree

1 file changed

+102
-4
lines changed

1 file changed

+102
-4
lines changed

packages/markdown-db/src/markdown-db.engine.ts

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { type FsDir, type FsFile, fsFile } from "@synstack/fs";
22
import { glob } from "@synstack/glob";
3+
import { md } from "@synstack/markdown";
34
import { QueryEngine } from "@synstack/query";
5+
import { t } from "@synstack/text";
46
import { z } from "zod/v4";
5-
import { getMarkdownEntries, NAME_SEPARATOR } from "./markdown-db.lib.ts";
7+
import { getMarkdownEntries } from "./markdown-db.lib.ts";
68

79
type Globs = [string, ...string[]];
810

@@ -14,6 +16,8 @@ type Entry<CONFIG_SCHEMA extends BaseConfigSchema> = Awaited<
1416
ReturnType<typeof getMarkdownEntries<CONFIG_SCHEMA>>
1517
>[number];
1618

19+
export const NAME_SEPARATOR = "/";
20+
1721
export class MarkdownDb<
1822
INPUT = unknown,
1923
CONFIG_SCHEMA extends BaseConfigSchema = BaseConfigSchema,
@@ -96,6 +100,18 @@ export class MarkdownDb<
96100
);
97101
}
98102

103+
/**
104+
* Filter the markdown files with glob patterns
105+
*/
106+
public setGlobs(...globs: Globs) {
107+
return new MarkdownDb(
108+
this._cwd,
109+
this._queryEngine,
110+
this._configSchema,
111+
globs,
112+
);
113+
}
114+
99115
/**
100116
* Check if the provided file is a markdown entry file in this db instance
101117
* @param file - The file to check
@@ -113,10 +129,92 @@ export class MarkdownDb<
113129
}
114130

115131
/**
116-
* Filter the markdown files with glob patterns
132+
* Compute the entry id
133+
* @param mdFile - The markdown file
134+
* @returns The entry id
117135
*/
118-
public setGlobs(...globs: Globs) {
119-
return new MarkdownDb(this._cwd, this._queryEngine, this._configSchema, globs);
136+
public computeEntryId(mdFile: FsFile) {
137+
const relativePath = mdFile.dir().relativePathFrom(this._cwd);
138+
const dirPath = relativePath.split("/");
139+
const lastFolderName = dirPath.pop();
140+
let fileName = mdFile.fileNameWithoutExtension();
141+
142+
// Remove numeric prefix (e.g., "0." from "0.buttons")
143+
fileName = fileName.replace(/^\d+\./, "");
144+
145+
// Extract type suffix if present (e.g., "my-type" from "buttons.my-type")
146+
let type: string | null = null;
147+
const typeMatch = fileName.match(/^(.+)\.(.+)$/);
148+
if (typeMatch) {
149+
fileName = typeMatch[1];
150+
type = typeMatch[2];
151+
}
152+
153+
// If the last folder's name is the same as the file name, we can skip it
154+
const nameParts =
155+
lastFolderName === fileName
156+
? [...dirPath, fileName]
157+
: [...dirPath, lastFolderName, fileName];
158+
159+
return {
160+
name: nameParts.filter((part) => part !== "").join(NAME_SEPARATOR),
161+
type,
162+
};
163+
}
164+
165+
/**
166+
* Parse the markdown file
167+
* @param file - The file to parse
168+
* @returns The parsed entry
169+
*/
170+
public async fileToEntry(file: FsFile | string) {
171+
const mdFile = fsFile(file);
172+
173+
// Check if the file is an entry file
174+
if (!this.isEntryFile(mdFile))
175+
throw new Error(t`
176+
File ${mdFile.path} is not an entry file in this MarkdownDb instance
177+
- Cwd: ${this._cwd.path}
178+
- Globs: ${this._globs.join(", ")}
179+
`);
180+
181+
// Read the file
182+
const mdText = await mdFile.read.text();
183+
184+
// Parse the header data
185+
const headerData = await new Promise((resolve) =>
186+
resolve(md.getHeaderData(mdText)),
187+
).catch(async (err) => {
188+
throw new Error(t`
189+
Failed to read markdown file header
190+
- File: ${this._cwd.relativePathTo(mdFile)}
191+
- Error:
192+
${err.message as string}
193+
`);
194+
});
195+
196+
// Validate the header data
197+
const parsedData = this._configSchema.safeParse(headerData);
198+
if (!parsedData.success)
199+
throw new Error(t`
200+
Failed to validate config for ${mdFile.path}:
201+
${z.prettifyError(parsedData.error)}
202+
`);
203+
204+
// Compute the entry id
205+
const { name, type } = this.computeEntryId(mdFile);
206+
207+
// Get the content
208+
const content = md.getBody(mdText).trim();
209+
210+
// Return the entry
211+
return {
212+
$id: name,
213+
$type: type,
214+
$content: content.length > 0 ? content : null,
215+
$file: mdFile,
216+
...parsedData.data,
217+
};
120218
}
121219

122220
/**

0 commit comments

Comments
 (0)