Skip to content

Commit f4805f0

Browse files
committed
refactor(store): merge importRecord into create() with optional slug/timestamps/version
Remove separate Import types and importRecord methods. Instead, extend *Create interfaces with optional slug, createdAt, updatedAt, version fields. When slug is provided and already exists, create() does upsert.
1 parent 8eca8ec commit f4805f0

10 files changed

Lines changed: 157 additions & 290 deletions

File tree

src/store/sqlite/lib/mirror-import.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function importMirrorDirs(
6969
if (!parsed) continue;
7070

7171
const embedding = embedFn(`${parsed.title} ${parsed.content}`);
72-
const record = scoped.knowledge.importRecord({
72+
const record = scoped.knowledge.create({
7373
slug: parsed.id,
7474
title: parsed.title,
7575
content: parsed.content,
@@ -94,7 +94,7 @@ export function importMirrorDirs(
9494
if (!parsed) continue;
9595

9696
const embedding = embedFn(`${parsed.title} ${parsed.description}`);
97-
const record = scoped.tasks.importRecord({
97+
const record = scoped.tasks.create({
9898
slug: parsed.id,
9999
title: parsed.title,
100100
description: parsed.description,
@@ -124,7 +124,7 @@ export function importMirrorDirs(
124124
if (!parsed) continue;
125125

126126
const embedding = embedFn(`${parsed.title} ${parsed.description}`);
127-
const record = scoped.skills.importRecord({
127+
const record = scoped.skills.create({
128128
slug: parsed.id,
129129
title: parsed.title,
130130
description: parsed.description,
@@ -157,7 +157,7 @@ export function importMirrorDirs(
157157
if (!parsed) continue;
158158

159159
const embedding = embedFn(`${parsed.title} ${parsed.description}`);
160-
const record = scoped.epics.importRecord({
160+
const record = scoped.epics.create({
161161
slug: parsed.id,
162162
title: parsed.title,
163163
description: parsed.description,

src/store/sqlite/stores/epics.ts

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { randomUUID } from 'crypto';
33
import type {
44
EpicsStore,
55
EpicCreate,
6-
EpicImport,
76
EpicPatch,
87
EpicRecord,
98
EpicDetail,
@@ -108,15 +107,38 @@ export class SqliteEpicsStore implements EpicsStore {
108107

109108
create(data: EpicCreate, embedding: number[]): EpicRecord {
110109
assertEmbeddingDim(embedding, this.embeddingDim);
111-
const slug = randomUUID();
110+
const slug = data.slug ?? randomUUID();
112111
const ts = now();
113112
const authorId = data.authorId ?? null;
114-
113+
const version = data.version ?? 1;
114+
const createdAt = data.createdAt ?? ts;
115+
const updatedAt = data.updatedAt ?? ts;
115116
const order = data.order ?? this.nextOrder();
117+
118+
// Upsert when slug is provided and already exists
119+
if (data.slug) {
120+
const existing = this.db.prepare('SELECT * FROM epics WHERE slug = ? AND project_id = ?').get(slug, this.projectId) as Record<string, unknown> | undefined;
121+
if (existing) {
122+
const id = num(existing.id as bigint);
123+
this.db.prepare(`
124+
UPDATE epics SET title = ?, description = ?, status = ?, priority = ?,
125+
"order" = ?, version = ?, updated_at = ?
126+
WHERE id = ? AND project_id = ?
127+
`).run(
128+
data.title, data.description ?? '', data.status ?? 'open', data.priority ?? 'medium',
129+
order, version, updatedAt, id, this.projectId,
130+
);
131+
this.db.prepare('DELETE FROM epics_vec WHERE rowid = ?').run(BigInt(id));
132+
this.db.prepare('INSERT INTO epics_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
133+
if (data.tags) this.helpers.setTags(GRAPH, id, data.tags);
134+
return this.toRecord(this.db.prepare('SELECT * FROM epics WHERE id = ? AND project_id = ?').get(id, this.projectId) as Record<string, unknown>);
135+
}
136+
}
137+
116138
const result = this.db.prepare(`
117139
INSERT INTO epics (project_id, slug, title, description, status, priority, "order", version, created_by_id, updated_by_id, created_at, updated_at)
118-
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)
119-
`).run(this.projectId, slug, data.title, data.description ?? '', data.status ?? 'open', data.priority ?? 'medium', order, authorId, authorId, ts, ts);
140+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
141+
`).run(this.projectId, slug, data.title, data.description ?? '', data.status ?? 'open', data.priority ?? 'medium', order, version, authorId, authorId, createdAt, updatedAt);
120142
const id = result.lastInsertRowid;
121143

122144
this.db.prepare('INSERT INTO epics_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id as number | bigint), Buffer.from(new Float32Array(embedding).buffer));
@@ -264,49 +286,6 @@ export class SqliteEpicsStore implements EpicsStore {
264286
// Meta
265287
// =========================================================================
266288

267-
importRecord(data: EpicImport, embedding: number[]): EpicRecord {
268-
assertEmbeddingDim(embedding, this.embeddingDim);
269-
270-
const existing = this.db.prepare('SELECT * FROM epics WHERE slug = ? AND project_id = ?').get(data.slug, this.projectId) as Record<string, unknown> | undefined;
271-
272-
if (existing) {
273-
const id = num(existing.id as bigint);
274-
this.db.prepare(`
275-
UPDATE epics SET title = ?, description = ?, status = ?, priority = ?,
276-
"order" = ?, version = ?, updated_at = ?
277-
WHERE id = ? AND project_id = ?
278-
`).run(
279-
data.title, data.description, data.status ?? 'open', data.priority ?? 'medium',
280-
data.order ?? num(existing.order as bigint | number),
281-
data.version, data.updatedAt, id, this.projectId,
282-
);
283-
284-
this.db.prepare('DELETE FROM epics_vec WHERE rowid = ?').run(BigInt(id));
285-
this.db.prepare('INSERT INTO epics_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
286-
287-
if (data.tags) this.helpers.setTags(GRAPH, id, data.tags);
288-
289-
return this.toRecord(this.db.prepare('SELECT * FROM epics WHERE id = ? AND project_id = ?').get(id, this.projectId) as Record<string, unknown>);
290-
}
291-
292-
const order = data.order ?? this.nextOrder();
293-
const result = this.db.prepare(`
294-
INSERT INTO epics (project_id, slug, title, description, status, priority, "order", version, created_at, updated_at)
295-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
296-
`).run(
297-
this.projectId, data.slug, data.title, data.description,
298-
data.status ?? 'open', data.priority ?? 'medium', order,
299-
data.version, data.createdAt, data.updatedAt,
300-
);
301-
const id = num(result.lastInsertRowid);
302-
303-
this.db.prepare('INSERT INTO epics_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
304-
305-
if (data.tags && data.tags.length > 0) this.helpers.setTags(GRAPH, id, data.tags);
306-
307-
return this.toRecord(this.db.prepare('SELECT * FROM epics WHERE id = ? AND project_id = ?').get(id, this.projectId) as Record<string, unknown>);
308-
}
309-
310289
getMeta(key: string): string | null { return this.meta.getMeta(key); }
311290
setMeta(key: string, value: string): void { this.meta.setMeta(key, value); }
312291
deleteMeta(key: string): void { this.meta.deleteMeta(key); }

src/store/sqlite/stores/knowledge.ts

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { randomUUID } from 'crypto';
33
import type {
44
KnowledgeStore,
55
NoteCreate,
6-
NoteImport,
76
NotePatch,
87
NoteRecord,
98
NoteDetail,
@@ -42,7 +41,7 @@ export class SqliteKnowledgeStore implements KnowledgeStore {
4241
return {
4342
insert: this.db.prepare(`
4443
INSERT INTO knowledge (project_id, slug, title, content, version, created_by_id, updated_by_id, created_at, updated_at)
45-
VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?)
44+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4645
`),
4746
insertVec: this.db.prepare('INSERT INTO knowledge_vec (rowid, embedding) VALUES (?, ?)'),
4847
deleteVec: this.db.prepare('DELETE FROM knowledge_vec WHERE rowid = ?'),
@@ -77,11 +76,30 @@ export class SqliteKnowledgeStore implements KnowledgeStore {
7776

7877
create(data: NoteCreate, embedding: number[]): NoteRecord {
7978
assertEmbeddingDim(embedding, this.embeddingDim);
80-
const slug = randomUUID();
79+
const slug = data.slug ?? randomUUID();
8180
const ts = now();
8281
const authorId = data.authorId ?? null;
82+
const version = data.version ?? 1;
83+
const createdAt = data.createdAt ?? ts;
84+
const updatedAt = data.updatedAt ?? ts;
85+
86+
// Upsert when slug is provided and already exists
87+
if (data.slug) {
88+
const existing = this.stmts.getBySlug.get(slug, this.projectId) as Record<string, unknown> | undefined;
89+
if (existing) {
90+
const id = num(existing.id as bigint);
91+
this.db.prepare(`
92+
UPDATE knowledge SET title = ?, content = ?, version = ?, updated_at = ?
93+
WHERE id = ? AND project_id = ?
94+
`).run(data.title, data.content, version, updatedAt, id, this.projectId);
95+
this.stmts.deleteVec.run(BigInt(id));
96+
this.stmts.insertVec.run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
97+
if (data.tags) this.helpers.setTags(GRAPH, id, data.tags);
98+
return this.toRecord(this.stmts.getById.get(id, this.projectId) as Record<string, unknown>);
99+
}
100+
}
83101

84-
const result = this.stmts.insert.run(this.projectId, slug, data.title, data.content, authorId, authorId, ts, ts);
102+
const result = this.stmts.insert.run(this.projectId, slug, data.title, data.content, version, authorId, authorId, createdAt, updatedAt);
85103
const id = result.lastInsertRowid;
86104

87105
this.stmts.insertVec.run(BigInt(id as number | bigint), Buffer.from(new Float32Array(embedding).buffer));
@@ -191,39 +209,6 @@ export class SqliteKnowledgeStore implements KnowledgeStore {
191209
return row ? num(row.updated_at) : null;
192210
}
193211

194-
importRecord(data: NoteImport, embedding: number[]): NoteRecord {
195-
assertEmbeddingDim(embedding, this.embeddingDim);
196-
197-
const existing = this.stmts.getBySlug.get(data.slug, this.projectId) as Record<string, unknown> | undefined;
198-
199-
if (existing) {
200-
const id = num(existing.id as bigint);
201-
this.db.prepare(`
202-
UPDATE knowledge SET title = ?, content = ?, version = ?, updated_at = ?
203-
WHERE id = ? AND project_id = ?
204-
`).run(data.title, data.content, data.version, data.updatedAt, id, this.projectId);
205-
206-
this.stmts.deleteVec.run(BigInt(id));
207-
this.stmts.insertVec.run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
208-
209-
if (data.tags) this.helpers.setTags(GRAPH, id, data.tags);
210-
211-
return this.toRecord(this.stmts.getById.get(id, this.projectId) as Record<string, unknown>);
212-
}
213-
214-
const result = this.db.prepare(`
215-
INSERT INTO knowledge (project_id, slug, title, content, version, created_at, updated_at)
216-
VALUES (?, ?, ?, ?, ?, ?, ?)
217-
`).run(this.projectId, data.slug, data.title, data.content, data.version, data.createdAt, data.updatedAt);
218-
const id = num(result.lastInsertRowid);
219-
220-
this.stmts.insertVec.run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
221-
222-
if (data.tags && data.tags.length > 0) this.helpers.setTags(GRAPH, id, data.tags);
223-
224-
return this.toRecord(this.stmts.getById.get(id, this.projectId) as Record<string, unknown>);
225-
}
226-
227212
getMeta(key: string): string | null { return this.meta.getMeta(key); }
228213
setMeta(key: string, value: string): void { this.meta.setMeta(key, value); }
229214
deleteMeta(key: string): void { this.meta.deleteMeta(key); }

src/store/sqlite/stores/skills.ts

Lines changed: 34 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { randomUUID } from 'crypto';
33
import type {
44
SkillsStore,
55
SkillCreate,
6-
SkillImport,
76
SkillPatch,
87
SkillRecord,
98
SkillDetail,
@@ -69,21 +68,51 @@ export class SqliteSkillsStore implements SkillsStore {
6968

7069
create(data: SkillCreate, embedding: number[]): SkillRecord {
7170
assertEmbeddingDim(embedding, this.embeddingDim);
72-
const slug = randomUUID();
71+
const slug = data.slug ?? randomUUID();
7372
const ts = now();
7473
const authorId = data.authorId ?? null;
74+
const version = data.version ?? 1;
75+
const createdAt = data.createdAt ?? ts;
76+
const updatedAt = data.updatedAt ?? ts;
77+
78+
// Upsert when slug is provided and already exists
79+
if (data.slug) {
80+
const existing = this.db.prepare('SELECT * FROM skills WHERE slug = ? AND project_id = ?').get(slug, this.projectId) as Record<string, unknown> | undefined;
81+
if (existing) {
82+
const id = num(existing.id as bigint);
83+
this.db.prepare(`
84+
UPDATE skills SET title = ?, description = ?,
85+
steps_json = ?, triggers_json = ?, input_hints_json = ?, file_patterns_json = ?,
86+
source = ?, confidence = ?, usage_count = ?, last_used_at = ?,
87+
version = ?, updated_at = ?
88+
WHERE id = ? AND project_id = ?
89+
`).run(
90+
data.title, data.description ?? '',
91+
JSON.stringify(data.steps ?? []), JSON.stringify(data.triggers ?? []),
92+
JSON.stringify(data.inputHints ?? []), JSON.stringify(data.filePatterns ?? []),
93+
data.source ?? 'user', data.confidence ?? 1.0,
94+
data.usageCount ?? 0, data.lastUsedAt ?? null,
95+
version, updatedAt, id, this.projectId,
96+
);
97+
this.db.prepare('DELETE FROM skills_vec WHERE rowid = ?').run(BigInt(id));
98+
this.db.prepare('INSERT INTO skills_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
99+
if (data.tags) this.helpers.setTags(GRAPH, id, data.tags);
100+
return this.toRecord(this.db.prepare('SELECT * FROM skills WHERE id = ? AND project_id = ?').get(id, this.projectId) as Record<string, unknown>);
101+
}
102+
}
75103

76104
const result = this.db.prepare(`
77-
INSERT INTO skills (project_id, slug, title, description, steps_json, triggers_json, input_hints_json, file_patterns_json, source, confidence, version, created_by_id, updated_by_id, created_at, updated_at)
78-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)
105+
INSERT INTO skills (project_id, slug, title, description, steps_json, triggers_json, input_hints_json, file_patterns_json, source, confidence, usage_count, last_used_at, version, created_by_id, updated_by_id, created_at, updated_at)
106+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
79107
`).run(
80108
this.projectId, slug, data.title, data.description ?? '',
81109
JSON.stringify(data.steps ?? []),
82110
JSON.stringify(data.triggers ?? []),
83111
JSON.stringify(data.inputHints ?? []),
84112
JSON.stringify(data.filePatterns ?? []),
85113
data.source ?? 'user', data.confidence ?? 1.0,
86-
authorId, authorId, ts, ts,
114+
data.usageCount ?? 0, data.lastUsedAt ?? null,
115+
version, authorId, authorId, createdAt, updatedAt,
87116
);
88117
const id = result.lastInsertRowid;
89118

@@ -199,56 +228,6 @@ export class SqliteSkillsStore implements SkillsStore {
199228
return row ? num(row.updated_at) : null;
200229
}
201230

202-
importRecord(data: SkillImport, embedding: number[]): SkillRecord {
203-
assertEmbeddingDim(embedding, this.embeddingDim);
204-
205-
const existing = this.db.prepare('SELECT * FROM skills WHERE slug = ? AND project_id = ?').get(data.slug, this.projectId) as Record<string, unknown> | undefined;
206-
207-
if (existing) {
208-
const id = num(existing.id as bigint);
209-
this.db.prepare(`
210-
UPDATE skills SET title = ?, description = ?,
211-
steps_json = ?, triggers_json = ?, input_hints_json = ?, file_patterns_json = ?,
212-
source = ?, confidence = ?, usage_count = ?, last_used_at = ?,
213-
version = ?, updated_at = ?
214-
WHERE id = ? AND project_id = ?
215-
`).run(
216-
data.title, data.description,
217-
JSON.stringify(data.steps ?? []), JSON.stringify(data.triggers ?? []),
218-
JSON.stringify(data.inputHints ?? []), JSON.stringify(data.filePatterns ?? []),
219-
data.source ?? 'user', data.confidence ?? 1.0,
220-
data.usageCount ?? 0, data.lastUsedAt ?? null,
221-
data.version, data.updatedAt, id, this.projectId,
222-
);
223-
224-
this.db.prepare('DELETE FROM skills_vec WHERE rowid = ?').run(BigInt(id));
225-
this.db.prepare('INSERT INTO skills_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
226-
227-
if (data.tags) this.helpers.setTags(GRAPH, id, data.tags);
228-
229-
return this.toRecord(this.db.prepare('SELECT * FROM skills WHERE id = ? AND project_id = ?').get(id, this.projectId) as Record<string, unknown>);
230-
}
231-
232-
const result = this.db.prepare(`
233-
INSERT INTO skills (project_id, slug, title, description, steps_json, triggers_json, input_hints_json, file_patterns_json, source, confidence, usage_count, last_used_at, version, created_at, updated_at)
234-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
235-
`).run(
236-
this.projectId, data.slug, data.title, data.description,
237-
JSON.stringify(data.steps ?? []), JSON.stringify(data.triggers ?? []),
238-
JSON.stringify(data.inputHints ?? []), JSON.stringify(data.filePatterns ?? []),
239-
data.source ?? 'user', data.confidence ?? 1.0,
240-
data.usageCount ?? 0, data.lastUsedAt ?? null,
241-
data.version, data.createdAt, data.updatedAt,
242-
);
243-
const id = num(result.lastInsertRowid);
244-
245-
this.db.prepare('INSERT INTO skills_vec (rowid, embedding) VALUES (?, ?)').run(BigInt(id), Buffer.from(new Float32Array(embedding).buffer));
246-
247-
if (data.tags && data.tags.length > 0) this.helpers.setTags(GRAPH, id, data.tags);
248-
249-
return this.toRecord(this.db.prepare('SELECT * FROM skills WHERE id = ? AND project_id = ?').get(id, this.projectId) as Record<string, unknown>);
250-
}
251-
252231
getMeta(key: string): string | null { return this.meta.getMeta(key); }
253232
setMeta(key: string, value: string): void { this.meta.setMeta(key, value); }
254233
deleteMeta(key: string): void { this.meta.deleteMeta(key); }

0 commit comments

Comments
 (0)