Skip to content

Commit 2f84914

Browse files
committed
update MarkdownDb
1 parent b263c6f commit 2f84914

File tree

3 files changed

+103
-47
lines changed

3 files changed

+103
-47
lines changed

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,17 @@ describe("PatternEngine", () => {
132132
describe("setGlob", () => {
133133
it("returns a new instance with custom glob pattern", () => {
134134
const engine1 = MarkdownDb.cwd(testPatternsDir);
135-
const engine2 = engine1.setGlob("simple/**/*.md");
135+
const engine2 = engine1.setGlobs("simple/**/*.md");
136136

137137
assert.notEqual(engine1, engine2);
138138
});
139139

140140
it("filters patterns using custom glob pattern", async () => {
141141
const engine = MarkdownDb.cwd<TestInput>(testPatternsDir)
142142
.setQueryEngine(createTestQueryEngine())
143-
.setGlob("simple/**/*.md");
143+
.setGlobs("simple/**/*.md");
144144

145-
const patterns = await engine.getEntries();
145+
const patterns = await engine.getAll();
146146
const patternNames = patterns.map((p) => p.$id);
147147

148148
// Should only find patterns from the simple directory
@@ -158,7 +158,7 @@ describe("PatternEngine", () => {
158158
createTestQueryEngine(),
159159
);
160160

161-
const patterns = await engine.getEntries();
161+
const patterns = await engine.getAll();
162162

163163
const basicPattern = patterns.find((p) => p.$id === "simple/basic");
164164
assert.ok(basicPattern);
@@ -177,7 +177,7 @@ describe("PatternEngine", () => {
177177
const engine = MarkdownDb.cwd<TestInput>(testPatternsDir).setQueryEngine(
178178
createTestQueryEngine(),
179179
);
180-
const patterns = await engine.getEntries();
180+
const patterns = await engine.getAll();
181181

182182
// The file nested/level1/level1.md should be named "nested/level1"
183183
const level1Pattern = patterns.find((p) => p.$id === "nested/level1");
@@ -194,7 +194,7 @@ describe("PatternEngine", () => {
194194
}),
195195
);
196196

197-
const patterns = await engine.getEntries();
197+
const patterns = await engine.getAll();
198198
const statusPattern = patterns.find(
199199
(p) => p.$id === "complex/with-status",
200200
);
@@ -208,7 +208,7 @@ describe("PatternEngine", () => {
208208
const engine = MarkdownDb.cwd<TestInput>(testPatternsDir).setQueryEngine(
209209
createTestQueryEngine(),
210210
);
211-
const patterns = await engine.getEntries();
211+
const patterns = await engine.getAll();
212212

213213
const names = patterns.map((p) => p.$id);
214214
const sortedNames = [...names].sort((a, b) => a.localeCompare(b));
@@ -228,7 +228,7 @@ query:
228228
const engine = MarkdownDb.cwd<TestInput>(
229229
testPatternsDir,
230230
).setQueryEngine(createTestQueryEngine());
231-
const patterns = await engine.getEntries();
231+
const patterns = await engine.getAll();
232232

233233
const emptyPromptPattern = patterns.find(
234234
(p) => p.$id === "empty-prompt" || p.$id === "/empty-prompt",
@@ -255,7 +255,7 @@ query:
255255
extension: "tsx",
256256
};
257257

258-
const matching = await engine.match(input);
258+
const matching = await engine.matchOne(input);
259259
const matchingNames = matching.map((p) => p.$id);
260260

261261
// Should match: simple/basic (always: true), complex/with-query (contains "component")
@@ -275,7 +275,7 @@ query:
275275
extension: "tsx",
276276
};
277277

278-
const matching = await engine.match(input);
278+
const matching = await engine.matchOne(input);
279279
const names = matching.map((p) => p.$id);
280280

281281
// Should match patterns based on query evaluation
@@ -300,7 +300,7 @@ query:
300300
{ content: "component", extension: "tsx" },
301301
];
302302

303-
const results = await engine.matchAll(inputs);
303+
const results = await engine.matchAny(inputs);
304304
const resultIds = results.map((result) => result.$id);
305305

306306
// Verify results are returned in sorted order by file path
@@ -374,7 +374,7 @@ Invalid pattern.`);
374374
const engine = MarkdownDb.cwd(testPatternsDir);
375375

376376
await assert.rejects(
377-
async () => await engine.getEntries(),
377+
async () => await engine.getAll(),
378378
/Failed to parse config/,
379379
);
380380

@@ -398,7 +398,7 @@ Missing query field.`);
398398
);
399399

400400
await assert.rejects(
401-
async () => await engine.getEntries(),
401+
async () => await engine.getAll(),
402402
/Failed to parse config/,
403403
);
404404

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

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { QueryEngine } from "@synstack/query";
33
import { z } from "zod/v4";
44
import { getMarkdownEntries, NAME_SEPARATOR } from "./markdown-db.lib.ts";
55

6+
type Globs = [string, ...string[]];
7+
68
type BaseConfigSchema = z.ZodObject<{
79
query: z.ZodType<unknown>;
810
}>;
@@ -18,7 +20,7 @@ export class MarkdownDb<
1820
private _configSchema;
1921
private _cwd;
2022
private _queryEngine;
21-
private _glob: string = "**/*.md";
23+
private _globs: Globs = ["**/*.md"];
2224
private _entriesPromise: Promise<Entry<CONFIG_SCHEMA>[]> | null = null;
2325
private _entriesMapPromise: Promise<
2426
Map<string, Entry<CONFIG_SCHEMA>>
@@ -31,14 +33,19 @@ export class MarkdownDb<
3133
cwd: FsDir,
3234
queryEngine: QueryEngine<any, INPUT>,
3335
configSchema: CONFIG_SCHEMA,
34-
glob: string = "**/*.md",
36+
globs: Globs = ["**/*.md"],
3537
) {
3638
this._cwd = cwd;
3739
this._queryEngine = queryEngine;
3840
this._configSchema = configSchema;
39-
this._glob = glob;
41+
this._globs = globs;
4042
}
4143

44+
/**
45+
* Create a MarkdownDb instance for the given directory
46+
* @param cwd - The FsDir or path to the directory to create the MarkdownDb instance for
47+
* @returns A new MarkdownDb instance
48+
*/
4249
public static cwd<INPUT = unknown>(cwd: FsDir) {
4350
const engine = QueryEngine.default<INPUT>();
4451
return new MarkdownDb<INPUT, BaseConfigSchema>(
@@ -48,18 +55,31 @@ export class MarkdownDb<
4855
);
4956
}
5057

58+
/**
59+
* @returns The QueryEngine
60+
*/
5161
public get query() {
5262
return this._queryEngine;
5363
}
5464

65+
/**
66+
* Set a custom query engine to use for matching with custom predicates
67+
* @param queryEngine - The QueryEngine to use
68+
* @returns A new MarkdownDb instance
69+
*/
5570
public setQueryEngine(queryEngine: QueryEngine<any, INPUT>) {
5671
// Update the config schema to use the new query engine's schema
5772
const newSchema = this._configSchema.omit({ query: true }).extend({
5873
query: queryEngine.schema,
5974
}) as CONFIG_SCHEMA;
60-
return new MarkdownDb(this._cwd, queryEngine, newSchema, this._glob);
75+
return new MarkdownDb(this._cwd, queryEngine, newSchema, this._globs);
6176
}
6277

78+
/**
79+
* Set the zod schema used to validate extra frontmatter data
80+
* @param configSchema - The zod schema to use to validate extra frontmatter data
81+
* @returns A new MarkdownDb instance
82+
*/
6383
public setConfigSchema<NEW_CONFIG_SCHEMA extends z.ZodObject<any>>(
6484
configSchema: NEW_CONFIG_SCHEMA,
6585
) {
@@ -71,59 +91,76 @@ export class MarkdownDb<
7191
}) as NEW_CONFIG_SCHEMA extends z.ZodObject<infer T>
7292
? z.ZodObject<T & { query: z.ZodType<unknown> }>
7393
: never,
74-
this._glob,
94+
this._globs,
7595
);
7696
}
7797

78-
public setGlob(glob: string) {
79-
return new MarkdownDb(
80-
this._cwd,
81-
this._queryEngine,
82-
this._configSchema,
83-
glob,
84-
);
98+
/**
99+
* Filter the markdown files with glob patterns
100+
* The "**\/*.md" glob is always included
101+
*/
102+
public setGlobs(...globs: Globs) {
103+
return new MarkdownDb(this._cwd, this._queryEngine, this._configSchema, [
104+
"**/*.md",
105+
...globs,
106+
]);
85107
}
86108

109+
/**
110+
* Refresh the markdown entries from the filesystem
111+
*/
87112
public async refreshEntries() {
88113
this._entriesPromise = getMarkdownEntries(
89114
this._cwd,
90115
this.schema,
91-
this._glob,
116+
this._globs,
92117
);
93118
this._entriesMapPromise = this._entriesPromise.then(
94119
(entries) => new Map(entries.map((entry) => [entry.$id, entry])),
95120
);
96121
this._parentPatternsMapPromise = null; // Reset parent entries cache
97122
}
98123

99-
public async getEntries() {
124+
/**
125+
* Return all entries as an array of unique entries
126+
*/
127+
public async getAll() {
100128
if (!this._entriesPromise) {
101129
this._entriesPromise = getMarkdownEntries(
102130
this._cwd,
103131
this.schema,
104-
this._glob,
132+
this._globs,
105133
);
106134
}
107135
return this._entriesPromise;
108136
}
109137

110-
public async getEntriesMap() {
138+
/**
139+
* Return the entries as a Map<id, entry> for quick lookup
140+
*/
141+
public async getAllMap() {
111142
if (!this._entriesMapPromise) {
112-
this._entriesMapPromise = this.getEntries().then(
143+
this._entriesMapPromise = this.getAll().then(
113144
(entries) => new Map(entries.map((entry) => [entry.$id, entry])),
114145
);
115146
}
116147
return this._entriesMapPromise!;
117148
}
118149

119-
public async getEntryById(id: string) {
120-
const entries = await this.getEntriesMap();
150+
/**
151+
* Return the entry for the given id
152+
*/
153+
public async getOneById(id: string) {
154+
const entries = await this.getAllMap();
121155
return entries.get(id);
122156
}
123157

124-
public async getParentEntriesMap() {
158+
/**
159+
* Return the parent entries for all entries
160+
*/
161+
public async getParentsMap() {
125162
if (!this._parentPatternsMapPromise) {
126-
this._parentPatternsMapPromise = this.getEntries().then((entries) => {
163+
this._parentPatternsMapPromise = this.getAll().then((entries) => {
127164
const entriesMap = new Map(entries.map((p) => [p.$id, p]));
128165
const parentMap = new Map<string, Entry<CONFIG_SCHEMA>[]>();
129166

@@ -150,23 +187,29 @@ export class MarkdownDb<
150187
return this._parentPatternsMapPromise!;
151188
}
152189

153-
public async getParentEntries(id: string) {
154-
const parentMap = await this.getParentEntriesMap();
190+
/**
191+
* Return the parent entries for the given id
192+
*/
193+
public async getParentsById(id: string) {
194+
const parentMap = await this.getParentsMap();
155195
return parentMap.get(id) || [];
156196
}
157197

158-
public async match(
198+
/**
199+
* Return all entries matching the input
200+
*/
201+
public async matchOne(
159202
input: INPUT,
160-
config: {
203+
config?: {
161204
/**
162205
* Skip markdown entries with empty content
163206
*/
164207
skipEmpty?: boolean;
165-
} = { skipEmpty: false },
208+
},
166209
) {
167210
// Retrieve all entries and parent entries map
168-
const entries = await this.getEntries();
169-
const parentMap = await this.getParentEntriesMap();
211+
const entries = await this.getAll();
212+
const parentMap = await this.getParentsMap();
170213

171214
const matchingEntries: Entry<CONFIG_SCHEMA>[] = [];
172215

@@ -191,15 +234,18 @@ export class MarkdownDb<
191234
skipQueryValidation: true,
192235
})
193236
) {
194-
if (config.skipEmpty && !entry.$content?.trim()) continue;
237+
if (config?.skipEmpty && !entry.$content?.trim()) continue;
195238
matchingEntries.push(entry);
196239
}
197240
}
198241

199242
return matchingEntries;
200243
}
201244

202-
public async matchAll(
245+
/**
246+
* Return all entries matching any of the inputs
247+
*/
248+
public async matchAny(
203249
inputs: INPUT[],
204250
config: {
205251
/**
@@ -209,7 +255,7 @@ export class MarkdownDb<
209255
} = { skipEmpty: false },
210256
) {
211257
const allResults = await Promise.all(
212-
inputs.map((input) => this.match(input, config)),
258+
inputs.map((input) => this.matchOne(input, config)),
213259
);
214260

215261
// Remove duplicates
@@ -225,10 +271,16 @@ export class MarkdownDb<
225271
).sort((a, b) => a.$file.path.localeCompare(b.$file.path));
226272
}
227273

274+
/**
275+
* Return the zod/v4 configuration schema
276+
*/
228277
public get schema() {
229278
return this._configSchema;
230279
}
231280

281+
/**
282+
* Return the JSON schema representation of the configuration schema
283+
*/
232284
public get jsonSchema() {
233285
return z.toJSONSchema(this.schema);
234286
}
@@ -245,7 +297,7 @@ export declare namespace MarkdownDb {
245297

246298
export namespace Entry {
247299
export type Infer<T extends MarkdownDb<any, any>> = Awaited<
248-
ReturnType<T["getEntries"]>
300+
ReturnType<T["getAll"]>
249301
>[number];
250302
}
251303
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ export function getMarkdownEntryId(mdDir: FsDir, mdFile: FsFile) {
3636

3737
export async function getMarkdownEntries<
3838
CONFIG_SCHEMA extends z.ZodObject<any>,
39-
>(cwd: FsDir, configSchema: CONFIG_SCHEMA, glob: string = "**/*.md") {
39+
>(
40+
cwd: FsDir,
41+
configSchema: CONFIG_SCHEMA,
42+
globs: [string, ...string[]] = ["**/*.md"],
43+
) {
4044
const mdFiles = await cwd
41-
.glob(glob)
45+
.glob(globs)
4246
// Sort by path
4347
.then((files) =>
4448
files.sort((a, b) =>

0 commit comments

Comments
 (0)