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.
- package/CHANGELOG.md +37 -0
- package/core/__tests__/storage/sqlite-migration.test.ts +1016 -0
- package/core/__tests__/storage/storage-manager.test.ts +42 -38
- package/core/services/sync-service.ts +5 -3
- package/core/storage/database.ts +551 -0
- package/core/storage/index-storage.ts +105 -96
- package/core/storage/index.ts +20 -30
- package/core/storage/migrate-json.ts +720 -0
- package/core/storage/storage-manager.ts +27 -49
- package/dist/bin/prjct.mjs +1857 -900
- package/package.json +1 -1
|
@@ -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
|
|
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 {
|
|
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,
|
|
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
|
|
32
|
+
* Get the kv_store key for this storage type.
|
|
33
|
+
* Derived from filename: 'state.json' → 'state'
|
|
34
34
|
*/
|
|
35
|
-
protected
|
|
36
|
-
return
|
|
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
|
|
76
|
-
*
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
96
|
-
|
|
92
|
+
} catch {
|
|
93
|
+
// SQLite not available (e.g., DB dir doesn't exist yet)
|
|
97
94
|
}
|
|
98
95
|
|
|
99
|
-
|
|
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
|
|
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
|
|
125
|
-
|
|
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
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|