prjct-cli 1.15.0 → 1.16.0

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.
@@ -1,11 +1,13 @@
1
1
  /**
2
- * Storage Manager Base Class
2
+ * Storage Manager Base Class (PRJ-303: SQLite-backed)
3
3
  *
4
4
  * Write-through pattern:
5
- * 1. Write JSON to storage/
5
+ * 1. Write to SQLite kv_store (primary)
6
6
  * 2. Regenerate MD in context/
7
7
  * 3. Publish event for backend sync
8
8
  *
9
+ * Read path: cache → SQLite → default
10
+ *
9
11
  * Subclasses implement specific data types (state, queue, ideas, shipped).
10
12
  */
11
13
 
@@ -13,27 +15,25 @@ import fs from 'node:fs/promises'
13
15
  import path from 'node:path'
14
16
  import { eventBus, type SyncEvent } from '../events'
15
17
  import pathManager from '../infrastructure/path-manager'
16
- import { isNotFoundError } from '../types/fs'
17
18
  import { TTLCache } from '../utils/cache'
18
19
  import { getTimestamp } from '../utils/date-helper'
19
- import { safeRead, type ValidationSchema } from './safe-reader'
20
+ import { prjctDb } from './database'
20
21
 
21
22
  export abstract class StorageManager<T> {
22
23
  protected filename: string
23
24
  protected cache: TTLCache<T>
24
- protected schema: ValidationSchema | null
25
25
 
26
- constructor(filename: string, schema?: ValidationSchema) {
26
+ constructor(filename: string, _schema?: unknown) {
27
27
  this.filename = filename
28
28
  this.cache = new TTLCache<T>({ ttl: 5000, maxSize: 50 })
29
- this.schema = schema ?? null
30
29
  }
31
30
 
32
31
  /**
33
- * Get file path for storage JSON
32
+ * Get the kv_store key for this storage type.
33
+ * Derived from filename: 'state.json' → 'state'
34
34
  */
35
- protected getStoragePath(projectId: string): string {
36
- return pathManager.getStoragePath(projectId, this.filename)
35
+ protected getStoreKey(): string {
36
+ return this.filename.replace('.json', '')
37
37
  }
38
38
 
39
39
  /**
@@ -72,9 +72,8 @@ export abstract class StorageManager<T> {
72
72
  protected abstract getEventType(action: 'update' | 'create' | 'delete'): string
73
73
 
74
74
  /**
75
- * Read data from storage with optional Zod validation.
76
- * When a schema is provided (via constructor), validates after JSON.parse.
77
- * On corruption: creates .backup file, logs warning, returns default.
75
+ * Read data from storage.
76
+ * Path: cache SQLite kv_store default
78
77
  */
79
78
  async read(projectId: string): Promise<T> {
80
79
  // Check cache first (with expiration)
@@ -83,48 +82,32 @@ export abstract class StorageManager<T> {
83
82
  return cached
84
83
  }
85
84
 
86
- const filePath = this.getStoragePath(projectId)
87
-
88
- if (this.schema) {
89
- // Validated read path
90
- const data = await safeRead<T>(filePath, this.schema)
85
+ // Try SQLite kv_store (primary store)
86
+ try {
87
+ const data = prjctDb.getDoc<T>(projectId, this.getStoreKey())
91
88
  if (data !== null) {
92
89
  this.cache.set(projectId, data)
93
90
  return data
94
91
  }
95
- // File missing or corrupted — return default
96
- return this.getDefault()
92
+ } catch {
93
+ // SQLite not available (e.g., DB dir doesn't exist yet)
97
94
  }
98
95
 
99
- // Unvalidated fallback (for subclasses without a schema)
100
- try {
101
- const content = await fs.readFile(filePath, 'utf-8')
102
- const data = JSON.parse(content) as T
103
- this.cache.set(projectId, data)
104
- return data
105
- } catch (error) {
106
- if (isNotFoundError(error) || error instanceof SyntaxError) {
107
- return this.getDefault()
108
- }
109
- throw error
110
- }
96
+ return this.getDefault()
111
97
  }
112
98
 
113
99
  /**
114
- * Write data to storage + regenerate context + publish event
100
+ * Write data to storage + regenerate context + publish event.
101
+ * SQLite primary + MD context regeneration.
115
102
  */
116
103
  async write(projectId: string, data: T): Promise<void> {
117
- const storagePath = this.getStoragePath(projectId)
118
104
  const contextPath = this.getContextPath(projectId, this.getMdFilename())
119
105
 
120
- // Ensure directories exist
121
- await fs.mkdir(path.dirname(storagePath), { recursive: true })
106
+ // Ensure context directory exists
122
107
  await fs.mkdir(path.dirname(contextPath), { recursive: true })
123
108
 
124
- // 1. Write JSON (atomic via temp file)
125
- const tempPath = `${storagePath}.${Date.now()}.tmp`
126
- await fs.writeFile(tempPath, JSON.stringify(data, null, 2), 'utf-8')
127
- await fs.rename(tempPath, storagePath)
109
+ // 1. Write to SQLite kv_store (primary)
110
+ prjctDb.setDoc(projectId, this.getStoreKey(), data)
128
111
 
129
112
  // 2. Regenerate MD for Claude
130
113
  const md = this.toMarkdown(data)
@@ -190,18 +173,13 @@ export abstract class StorageManager<T> {
190
173
  }
191
174
 
192
175
  /**
193
- * Check if storage file exists
176
+ * Check if storage exists for this project.
194
177
  */
195
178
  async exists(projectId: string): Promise<boolean> {
196
- const filePath = this.getStoragePath(projectId)
197
179
  try {
198
- await fs.access(filePath)
199
- return true
200
- } catch (error) {
201
- if (isNotFoundError(error)) {
202
- return false
203
- }
204
- throw error
180
+ return prjctDb.hasDoc(projectId, this.getStoreKey())
181
+ } catch {
182
+ return false
205
183
  }
206
184
  }
207
185