import sqlite3 from 'better-sqlite3' import * as z from 'zod' const zMatter = z.object({ date: z.date().optional(), createdAt: z.date().optional(), updatedAt: z.date().optional(), title: z.string(), image: z.string().optional(), tag: z.array(z.string()).optional(), category: z.string().optional(), }) export type IMatter = z.infer export const zPost = z .object({ path: z.string(), /** * Cleaned and jieba-separated markdown */ content: z.string(), excerptHtml: z.string(), contentHtml: z.string(), }) .merge(zMatter.nonstrict()) export type IPost = z.infer export class Database { db: sqlite3.Database constructor(public filepath: string, opts?: sqlite3.Options) { this.db = sqlite3(filepath, opts) } public init(): void { this.db.exec(/* sql */ ` CREATE TABLE IF NOT EXISTS files ( [path] TEXT PRIMARY KEY, [date] INT, -- epoch milli [createdAt] INT, -- epoch milli [updatedAt] INT, -- epoch milli -- q.title [image] TEXT, -- q.tag.split(/ /g) -- q.category -- q.content excerptHtml TEXT NOT NULL, contentHtml TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS files_date ON files([date]); CREATE INDEX IF NOT EXISTS files_createdAt ON files(createdAt); CREATE INDEX IF NOT EXISTS files_updatedAt ON files(updatedAt); `) this.db.exec(/* sql */ ` CREATE VIRTUAL TABLE IF NOT EXISTS q USING fts5 ( [path], -- REFERENCES files([path]) title, tag, -- space separated category, -- can compare via equal content -- cleaned markdown ); `) } public insert(...ps: IPost[]): void { const fStmt = this.db.prepare(/* sql */ ` INSERT INTO files ([path], [date], [createdAt], [updatedAt], [image], excerptHtml, contentHtml) VALUES (@path, @date, @createdAt, @updatedAt, @image, @excerptHtml, @contentHtml) `) const qStmt = this.db.prepare(/* sql */ ` INSERT INTO q ([path], title, tag, category, content) VALUES (@path, @title, @tag, @category, @content) `) this.db.transaction(() => { // eslint-disable-next-line array-callback-return ps.map((p) => { p = zPost.parse(p) fStmt.run({ ...p, date: p.date ? p.date.getTime() : undefined, createdAt: p.createdAt ? p.createdAt.getTime() : undefined, updatedAt: p.updatedAt ? p.updatedAt.getTime() : undefined, }) qStmt.run({ ...p, tag: p.tag ? p.tag.join(' ') : undefined, }) }) })() } }