()
| 324 | // ── Lazy table init ───────────────────────────────────────────── |
| 325 | |
| 326 | private async ensureInit(): Promise<void> { |
| 327 | if (this.initialized) return; |
| 328 | this.initialized = true; |
| 329 | |
| 330 | const T = this.tableName; |
| 331 | const I = this.indexName; |
| 332 | |
| 333 | await this.sql.run(` |
| 334 | CREATE TABLE IF NOT EXISTS ${T} ( |
| 335 | path TEXT PRIMARY KEY, |
| 336 | parent_path TEXT NOT NULL, |
| 337 | name TEXT NOT NULL, |
| 338 | type TEXT NOT NULL CHECK(type IN ('file','directory','symlink')), |
| 339 | mime_type TEXT NOT NULL DEFAULT 'text/plain', |
| 340 | size INTEGER NOT NULL DEFAULT 0, |
| 341 | storage_backend TEXT NOT NULL DEFAULT 'inline' CHECK(storage_backend IN ('inline','r2')), |
| 342 | r2_key TEXT, |
| 343 | target TEXT, |
| 344 | content_encoding TEXT NOT NULL DEFAULT 'utf8', |
| 345 | content TEXT, |
| 346 | created_at INTEGER NOT NULL DEFAULT (unixepoch()), |
| 347 | modified_at INTEGER NOT NULL DEFAULT (unixepoch()) |
| 348 | ) |
| 349 | `); |
| 350 | |
| 351 | await this.sql.run(`CREATE INDEX IF NOT EXISTS ${I} ON ${T}(parent_path)`); |
| 352 | |
| 353 | const hasRoot = |
| 354 | ( |
| 355 | await this.sql.query<{ cnt: number }>( |
| 356 | `SELECT COUNT(*) AS cnt FROM ${T} WHERE path = '/'` |
| 357 | ) |
| 358 | )[0]?.cnt ?? 0; |
| 359 | |
| 360 | if (hasRoot === 0) { |
| 361 | const now = Math.floor(Date.now() / 1000); |
| 362 | await this.sql.run( |
| 363 | `INSERT INTO ${T} |
| 364 | (path, parent_path, name, type, size, created_at, modified_at) |
| 365 | VALUES ('/', '', '', 'directory', 0, ?, ?)`, |
| 366 | now, |
| 367 | now |
| 368 | ); |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | // ── R2 helpers ───────────────────────────────────────────────── |
| 373 |
no test coverage detected