@chatcops/store-sqlite 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CoderCops
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ # @chatcops/store-sqlite
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ SQLite storage for ChatCops single-server deployments. It uses Node.js 22.13+'s built-in `node:sqlite`, so no native database dependency is required.
4
+
5
+ Node may print an `ExperimentalWarning` the first time `node:sqlite` loads; this is expected from Node and is not an error.
6
+
7
+ ```bash
8
+ pnpm add @chatcops/store-sqlite
9
+ ```
10
+
11
+ ```ts
12
+ import { chatcopsMiddleware } from '@chatcops/server';
13
+ import { sqliteStorage } from '@chatcops/store-sqlite';
14
+
15
+ const storage = sqliteStorage({ path: './chatcops.db' });
16
+
17
+ app.use('/api/chat', chatcopsMiddleware({
18
+ provider,
19
+ systemPrompt: 'You are a helpful assistant.',
20
+ storage,
21
+ }));
22
+ ```
23
+
24
+ Use `path: ':memory:'` for tests, or pass an already-open `node:sqlite` `DatabaseSync` (or structurally compatible better-sqlite3 database) with `database`.
25
+
26
+ SQLite is intended here for one server process and a local file. Back up the database file using your normal deployment process. Call `await storage.close?.()` during graceful shutdown.
27
+
28
+ ## Extra columns
29
+
30
+ To stamp columns of your own (a tenant or site id) on every new row, add them with your own `ALTER TABLE` and pass `extraColumns` with a `rowHook`:
31
+
32
+ ```ts
33
+ database.exec('ALTER TABLE chatcops_conversations ADD COLUMN workspace_id TEXT');
34
+ database.exec('ALTER TABLE chatcops_messages ADD COLUMN workspace_id TEXT');
35
+
36
+ const storage = sqliteStorage({
37
+ database,
38
+ extraColumns: ['workspace_id'],
39
+ rowHook: ({ metadata }) => ({ workspace_id: metadata?.workspaceId }),
40
+ });
41
+ ```
42
+
43
+ The hook runs when a row is created, never on updates. It gets the table, the conversation id, the metadata passed with the write, and the message for message rows. Returning a key that isn't in `extraColumns` throws and nothing from that write is stored. Values are bound as parameters, never put into the SQL, and must be strings, numbers, booleans (stored as 1 or 0) or null.
44
+
45
+ The `{ workspace_id: 'uuid' }` form from `@chatcops/store-postgres` works too, so one config fits both stores. SQLite ignores the type.
@@ -0,0 +1,18 @@
1
+ export interface SqliteStatement {
2
+ run(...params: unknown[]): unknown;
3
+ get(...params: unknown[]): unknown;
4
+ all(...params: unknown[]): unknown[];
5
+ }
6
+ export interface SqliteDatabase {
7
+ prepare(sql: string): SqliteStatement;
8
+ exec(sql: string): void;
9
+ close?(): void;
10
+ loadExtension?(path: string): void;
11
+ }
12
+ export type SqliteDatabaseLike = SqliteDatabase;
13
+ export declare function openDatabase(path: string, allowExtension?: boolean): SqliteDatabase;
14
+ export declare function run<T>(db: SqliteDatabase, sql: string, ...params: unknown[]): T;
15
+ export declare function get<T>(db: SqliteDatabase, sql: string, ...params: unknown[]): T | undefined;
16
+ export declare function all<T>(db: SqliteDatabase, sql: string, ...params: unknown[]): T[];
17
+ export declare function transaction<T>(db: SqliteDatabase, operation: () => T): T;
18
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACnC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACnC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,aAAa,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAEhD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,UAAQ,GAAG,cAAc,CAEjF;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAE/E;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,SAAS,CAE3F;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAEjF;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAUxE"}
@@ -0,0 +1,29 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ export function openDatabase(path, allowExtension = false) {
3
+ return new DatabaseSync(path, { allowExtension });
4
+ }
5
+ export function run(db, sql, ...params) {
6
+ return db.prepare(sql).run(...params);
7
+ }
8
+ export function get(db, sql, ...params) {
9
+ return db.prepare(sql).get(...params);
10
+ }
11
+ export function all(db, sql, ...params) {
12
+ return db.prepare(sql).all(...params);
13
+ }
14
+ export function transaction(db, operation) {
15
+ db.exec('BEGIN IMMEDIATE');
16
+ try {
17
+ const value = operation();
18
+ db.exec('COMMIT');
19
+ return value;
20
+ }
21
+ catch (error) {
22
+ try {
23
+ db.exec('ROLLBACK');
24
+ }
25
+ catch { /* preserve the original error */ }
26
+ throw error;
27
+ }
28
+ }
29
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB3C,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,cAAc,GAAG,KAAK;IAC/D,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,CAA8B,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,GAAG,CAAI,EAAkB,EAAE,GAAW,EAAE,GAAG,MAAiB;IAC1E,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAM,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,GAAG,CAAI,EAAkB,EAAE,GAAW,EAAE,GAAG,MAAiB;IAC1E,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAkB,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,GAAG,CAAI,EAAkB,EAAE,GAAW,EAAE,GAAG,MAAiB;IAC1E,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAQ,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,EAAkB,EAAE,SAAkB;IACnE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC,CAAC;QACxE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type ChatCopsStorage } from '@chatcops/core';
2
+ import { type SqliteDatabase } from './database.js';
3
+ import { type ExtraColumns, type RowHookFunction } from './row-hook.js';
4
+ export type { ExtraColumns, RowHookContext, RowHookFunction } from './row-hook.js';
5
+ export interface SqliteStorageOptions {
6
+ /** Opt in to sqlite-vec embeddings and FTS5 keyword search. */
7
+ vectors?: {
8
+ dimensions: number;
9
+ };
10
+ /** A database file, or `:memory:`. */
11
+ path?: string;
12
+ /** An open `node:sqlite` `DatabaseSync`, or a compatible better-sqlite3 database. */
13
+ database?: SqliteDatabase;
14
+ /**
15
+ * Columns you've added to the conversations and messages tables (with your
16
+ * own `ALTER TABLE`), filled in by `rowHook` when a row is created.
17
+ */
18
+ extraColumns?: ExtraColumns;
19
+ /**
20
+ * Values for `extraColumns` on each new conversation and message row. Runs
21
+ * on insert only, never on update. Returning a key that isn't declared in
22
+ * `extraColumns` throws, and nothing from that write is stored. Values are
23
+ * bound as parameters, never put into the SQL.
24
+ */
25
+ rowHook?: RowHookFunction;
26
+ }
27
+ export declare function sqliteStorage(options: SqliteStorageOptions): ChatCopsStorage;
28
+ export type { SqliteDatabase } from './database.js';
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EASrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAA4C,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,EAAgB,KAAK,YAAY,EAAqC,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAIzH,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnF,MAAM,WAAW,oBAAoB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAmUD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,eAAe,CAqB5E;AAED,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,240 @@
1
+ import { ConflictError, } from '@chatcops/core';
2
+ import { all, get, openDatabase, run, transaction } from './database.js';
3
+ import { checkRowHook } from './row-hook.js';
4
+ import { initializeSchema } from './schema.js';
5
+ import { SqliteVectors } from './vectors.js';
6
+ const json = (value) => value === undefined ? null : JSON.stringify(value);
7
+ const changes = (result) => Number(result.changes ?? 0);
8
+ const now = () => Date.now();
9
+ class SqliteConversations {
10
+ db;
11
+ clock;
12
+ hook;
13
+ conversationInsert;
14
+ messageInsert;
15
+ constructor(db, clock, hook) {
16
+ this.db = db;
17
+ this.clock = clock;
18
+ this.hook = hook;
19
+ const extra = hook?.columns.map((name) => `, ${name}`).join('') ?? '';
20
+ const marks = hook?.columns.map(() => ', ?').join('') ?? '';
21
+ this.conversationInsert = `INSERT INTO chatcops_conversations (id, metadata, version, created_at, updated_at${extra}) VALUES (?, ?, ?, ?, ?${marks})`;
22
+ this.messageInsert = `INSERT INTO chatcops_messages (conversation_id, seq, id, message${extra}) VALUES (?, ?, ?, ?${marks})`;
23
+ }
24
+ /** The hook's values for one new row, or nothing without a hook. */
25
+ extra(context) {
26
+ return this.hook ? this.hook.values(context) : [];
27
+ }
28
+ insertMessages(id, after, messages, metadata) {
29
+ for (const [index, message] of messages.entries()) {
30
+ run(this.db, this.messageInsert, id, after + index + 1, message.id, JSON.stringify(message), ...this.extra({ table: 'messages', conversationId: id, metadata, message }));
31
+ }
32
+ }
33
+ get(id) {
34
+ const row = get(this.db, 'SELECT * FROM chatcops_conversations WHERE id = ?', id);
35
+ if (!row)
36
+ return Promise.resolve(null);
37
+ const messages = all(this.db, 'SELECT message FROM chatcops_messages WHERE conversation_id = ? ORDER BY seq', id).map((m) => JSON.parse(m.message));
38
+ return Promise.resolve({
39
+ id: row.id,
40
+ messages,
41
+ ...(row.metadata ? { metadata: JSON.parse(row.metadata) } : {}),
42
+ version: row.version,
43
+ createdAt: row.created_at,
44
+ updatedAt: row.updated_at,
45
+ });
46
+ }
47
+ save(conversation) {
48
+ transaction(this.db, () => {
49
+ const current = get(this.db, 'SELECT version FROM chatcops_conversations WHERE id = ?', conversation.id);
50
+ const version = (current?.version ?? 0) + 1;
51
+ // The hook's columns are only in the INSERT half, so an update never changes them.
52
+ run(this.db, `${this.conversationInsert}
53
+ ON CONFLICT(id) DO UPDATE SET
54
+ metadata = excluded.metadata,
55
+ version = excluded.version,
56
+ created_at = excluded.created_at,
57
+ updated_at = excluded.updated_at`, conversation.id, json(conversation.metadata), version, conversation.createdAt, this.clock(), ...this.extra({ table: 'conversations', conversationId: conversation.id, metadata: conversation.metadata }));
58
+ run(this.db, 'DELETE FROM chatcops_messages WHERE conversation_id = ?', conversation.id);
59
+ this.insertMessages(conversation.id, 0, conversation.messages, conversation.metadata);
60
+ });
61
+ return Promise.resolve();
62
+ }
63
+ delete(id) {
64
+ run(this.db, 'DELETE FROM chatcops_conversations WHERE id = ?', id);
65
+ return Promise.resolve();
66
+ }
67
+ async append(id, messages, options = {}) {
68
+ const version = transaction(this.db, () => {
69
+ const current = get(this.db, 'SELECT version, metadata FROM chatcops_conversations WHERE id = ?', id);
70
+ const actual = current?.version ?? 0;
71
+ if (options.expectedVersion !== undefined && options.expectedVersion !== actual)
72
+ throw new ConflictError(id, options.expectedVersion, actual);
73
+ const time = this.clock();
74
+ const metadata = options.metadata || current?.metadata
75
+ ? { ...(current?.metadata ? JSON.parse(current.metadata) : {}), ...(options.metadata ?? {}) }
76
+ : undefined;
77
+ if (!current) {
78
+ run(this.db, this.conversationInsert, id, json(metadata), 1, time, time, ...this.extra({ table: 'conversations', conversationId: id, metadata: options.metadata }));
79
+ }
80
+ else {
81
+ run(this.db, 'UPDATE chatcops_conversations SET metadata = ?, version = version + 1, updated_at = ? WHERE id = ?', json(metadata), time, id);
82
+ }
83
+ const last = get(this.db, 'SELECT COALESCE(MAX(seq), 0) AS seq FROM chatcops_messages WHERE conversation_id = ?', id)?.seq ?? 0;
84
+ this.insertMessages(id, last, messages, options.metadata);
85
+ return actual + 1;
86
+ });
87
+ return { version };
88
+ }
89
+ updateMessage(id, message) {
90
+ const result = transaction(this.db, () => {
91
+ const updated = run(this.db, 'UPDATE chatcops_messages SET message = ? WHERE conversation_id = ? AND id = ?', JSON.stringify(message), id, message.id);
92
+ if (!changes(updated))
93
+ return false;
94
+ run(this.db, 'UPDATE chatcops_conversations SET version = version + 1, updated_at = ? WHERE id = ?', this.clock(), id);
95
+ return true;
96
+ });
97
+ return Promise.resolve(result);
98
+ }
99
+ removeMessage(id, messageId) {
100
+ const result = transaction(this.db, () => {
101
+ const removed = run(this.db, 'DELETE FROM chatcops_messages WHERE conversation_id = ? AND id = ?', id, messageId);
102
+ if (!changes(removed))
103
+ return false;
104
+ run(this.db, 'UPDATE chatcops_conversations SET version = version + 1, updated_at = ? WHERE id = ?', this.clock(), id);
105
+ return true;
106
+ });
107
+ return Promise.resolve(result);
108
+ }
109
+ list(query) {
110
+ const separator = query.cursor?.indexOf(':') ?? -1;
111
+ const cursorTime = separator === -1 ? null : Number(query.cursor?.slice(0, separator));
112
+ const cursorId = separator === -1 ? null : query.cursor?.slice(separator + 1) ?? null;
113
+ const rows = all(this.db, `SELECT c.*, COUNT(m.seq) AS message_count
114
+ FROM chatcops_conversations c
115
+ LEFT JOIN chatcops_messages m ON m.conversation_id = c.id
116
+ WHERE (? IS NULL OR c.updated_at > ?)
117
+ AND (? IS NULL OR c.updated_at < ? OR (c.updated_at = ? AND c.id > ?))
118
+ GROUP BY c.id
119
+ ORDER BY c.updated_at DESC, c.id ASC
120
+ LIMIT ?`, query.updatedAfter ?? null, query.updatedAfter ?? null, cursorTime, cursorTime, cursorTime, cursorId, query.limit + 1);
121
+ const page = rows.slice(0, query.limit);
122
+ const items = page.map((row) => ({
123
+ id: row.id,
124
+ createdAt: row.created_at,
125
+ updatedAt: row.updated_at,
126
+ messageCount: Number(row.message_count),
127
+ ...(row.metadata ? { metadata: JSON.parse(row.metadata) } : {}),
128
+ }));
129
+ const last = page.at(-1);
130
+ return Promise.resolve(rows.length > query.limit && last ? { items, cursor: `${last.updated_at}:${last.id}` } : { items });
131
+ }
132
+ purge(before) {
133
+ return Promise.resolve(changes(run(this.db, 'DELETE FROM chatcops_conversations WHERE updated_at < ?', before)));
134
+ }
135
+ }
136
+ class SqliteCounters {
137
+ db;
138
+ clock;
139
+ constructor(db, clock) {
140
+ this.db = db;
141
+ this.clock = clock;
142
+ }
143
+ increment(key, by, ttlMs) {
144
+ const time = this.clock();
145
+ const row = get(this.db, `INSERT INTO chatcops_counters (key, value, expires_at)
146
+ VALUES (?, ?, ?)
147
+ ON CONFLICT(key) DO UPDATE SET
148
+ value = CASE WHEN chatcops_counters.expires_at <= ?
149
+ THEN excluded.value ELSE chatcops_counters.value + excluded.value END,
150
+ expires_at = CASE WHEN chatcops_counters.expires_at <= ?
151
+ THEN excluded.expires_at ELSE chatcops_counters.expires_at END
152
+ RETURNING value, expires_at`, key, by, time + ttlMs, time, time);
153
+ if (!row)
154
+ throw new Error('SQLite counter upsert did not return a row');
155
+ return Promise.resolve({ value: Number(row.value), expiresAt: Number(row.expires_at) });
156
+ }
157
+ get(key) {
158
+ const row = get(this.db, 'SELECT value, expires_at FROM chatcops_counters WHERE key = ? AND expires_at > ?', key, this.clock());
159
+ return Promise.resolve(row ? { value: Number(row.value), expiresAt: Number(row.expires_at) } : null);
160
+ }
161
+ }
162
+ class SqliteKv {
163
+ db;
164
+ clock;
165
+ constructor(db, clock) {
166
+ this.db = db;
167
+ this.clock = clock;
168
+ }
169
+ get(key) {
170
+ const row = get(this.db, 'SELECT value FROM chatcops_kv WHERE key = ? AND (expires_at IS NULL OR expires_at > ?)', key, this.clock());
171
+ return Promise.resolve(row ? JSON.parse(row.value) : null);
172
+ }
173
+ set(key, value, options = {}) {
174
+ run(this.db, `INSERT INTO chatcops_kv (key, value, expires_at)
175
+ VALUES (?, ?, ?)
176
+ ON CONFLICT(key) DO UPDATE SET
177
+ value = excluded.value,
178
+ expires_at = excluded.expires_at`, key, JSON.stringify(value), options.ttlMs === undefined ? null : this.clock() + options.ttlMs);
179
+ return Promise.resolve();
180
+ }
181
+ setIfAbsent(key, value, options = {}) {
182
+ const result = run(this.db, `INSERT INTO chatcops_kv (key, value, expires_at)
183
+ VALUES (?, ?, ?)
184
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at
185
+ WHERE chatcops_kv.expires_at IS NOT NULL AND chatcops_kv.expires_at <= ?`, key, JSON.stringify(value), options.ttlMs === undefined ? null : this.clock() + options.ttlMs, this.clock());
186
+ return Promise.resolve(changes(result) > 0);
187
+ }
188
+ delete(key) {
189
+ run(this.db, 'DELETE FROM chatcops_kv WHERE key = ?', key);
190
+ return Promise.resolve();
191
+ }
192
+ }
193
+ class SqliteEvents {
194
+ db;
195
+ constructor(db) {
196
+ this.db = db;
197
+ }
198
+ write(events) {
199
+ for (const event of events) {
200
+ run(this.db, 'INSERT INTO chatcops_events (type, data, created_at) VALUES (?, ?, ?)', event.type, json(event.data), event.timestamp);
201
+ }
202
+ return Promise.resolve();
203
+ }
204
+ query(query) {
205
+ const size = query.groupBy === 'hour' ? 3_600_000 : 86_400_000;
206
+ const rows = all(this.db, `SELECT CAST((created_at / ?) AS INTEGER) * ? AS bucket, type, COUNT(*) AS count
207
+ FROM chatcops_events
208
+ WHERE created_at >= ? AND created_at < ? AND (? IS NULL OR type = ?)
209
+ GROUP BY bucket, type
210
+ ORDER BY bucket, type`, size, size, query.from, query.to, query.type ?? null, query.type ?? null);
211
+ return Promise.resolve(rows.map((row) => ({ bucket: Number(row.bucket), type: row.type, count: Number(row.count) })));
212
+ }
213
+ }
214
+ export function sqliteStorage(options) {
215
+ if ((!options.path && !options.database) || (options.path && options.database))
216
+ throw new Error('sqliteStorage requires exactly one of path or database');
217
+ const hook = checkRowHook(options.extraColumns, options.rowHook);
218
+ const database = options.database ?? openDatabase(options.path ?? '', !!options.vectors);
219
+ initializeSchema(database);
220
+ let vectors;
221
+ try {
222
+ if (options.vectors)
223
+ vectors = new SqliteVectors(database, options.vectors.dimensions);
224
+ }
225
+ catch (error) {
226
+ if (!options.database)
227
+ database.close?.();
228
+ throw error;
229
+ }
230
+ const clock = now;
231
+ return {
232
+ ...(vectors ? { vectors } : {}),
233
+ conversations: new SqliteConversations(database, clock, hook),
234
+ counters: new SqliteCounters(database, clock),
235
+ kv: new SqliteKv(database, clock),
236
+ events: new SqliteEvents(database),
237
+ close: options.database ? async () => { } : async () => database.close?.(),
238
+ };
239
+ }
240
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ChatMessage } from '@chatcops/core';
2
+ /** What the hook gets for each new row. */
3
+ export interface RowHookContext {
4
+ table: 'conversations' | 'messages';
5
+ conversationId: string;
6
+ /** The metadata passed with this write (`append` options or the saved conversation). */
7
+ metadata?: Record<string, unknown>;
8
+ /** The message being inserted, for the messages table. */
9
+ message?: ChatMessage;
10
+ }
11
+ export type RowHookFunction = (context: RowHookContext) => Record<string, unknown>;
12
+ /**
13
+ * Columns you've added to the conversations and messages tables, by name.
14
+ * The `{ name: type }` form from `@chatcops/store-postgres` works too, so one
15
+ * config fits both stores; SQLite stores each value with its own type, so the
16
+ * type is ignored here.
17
+ */
18
+ export type ExtraColumns = string[] | Record<string, string>;
19
+ /** A checked hook: the columns it may set, and a function that refuses any others. */
20
+ export interface RowHook {
21
+ columns: string[];
22
+ /** The values for `columns`, in order, ready to bind. */
23
+ values(context: RowHookContext): unknown[];
24
+ }
25
+ export declare function checkRowHook(extraColumns: ExtraColumns | undefined, rowHook: RowHookFunction | undefined): RowHook | undefined;
26
+ //# sourceMappingURL=row-hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-hook.d.ts","sourceRoot":"","sources":["../src/row-hook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,eAAe,GAAG,UAAU,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAK7D,sFAAsF;AACtF,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yDAAyD;IACzD,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,EAAE,CAAC;CAC5C;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAuB9H"}
@@ -0,0 +1,39 @@
1
+ const IDENTIFIER = /^[a-z_][a-z0-9_]{0,62}$/;
2
+ const RESERVED = new Set(['id', 'metadata', 'version', 'created_at', 'updated_at', 'conversation_id', 'seq', 'message']);
3
+ export function checkRowHook(extraColumns, rowHook) {
4
+ if (!rowHook && !extraColumns)
5
+ return undefined;
6
+ if (!rowHook || !extraColumns)
7
+ throw new Error('sqliteStorage needs both extraColumns and rowHook, or neither.');
8
+ const columns = Array.isArray(extraColumns) ? extraColumns : Object.keys(extraColumns);
9
+ for (const name of columns) {
10
+ if (!IDENTIFIER.test(name))
11
+ throw new Error(`extraColumns: "${name}" isn't a plain lowercase column name.`);
12
+ if (RESERVED.has(name))
13
+ throw new Error(`extraColumns: "${name}" is one of the store's own columns.`);
14
+ }
15
+ const allowed = new Set(columns);
16
+ return {
17
+ columns,
18
+ values(context) {
19
+ const row = rowHook(context) ?? {};
20
+ for (const key of Object.keys(row)) {
21
+ if (!allowed.has(key)) {
22
+ throw new Error(`rowHook returned "${key}", which isn't in extraColumns. Declare it there first.`);
23
+ }
24
+ }
25
+ return columns.map((name) => bindable(name, row[name]));
26
+ },
27
+ };
28
+ }
29
+ /** node:sqlite binds strings, numbers, bigints, bytes and null. Booleans are stored as the integers 1 and 0. */
30
+ function bindable(name, value) {
31
+ if (value === undefined || value === null)
32
+ return null;
33
+ if (typeof value === 'boolean')
34
+ return value ? 1n : 0n;
35
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || value instanceof Uint8Array)
36
+ return value;
37
+ throw new Error(`rowHook returned an unsupported value (${typeof value}) for "${name}". Use a string, number, boolean or null.`);
38
+ }
39
+ //# sourceMappingURL=row-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-hook.js","sourceRoot":"","sources":["../src/row-hook.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,GAAG,yBAAyB,CAAC;AAC7C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;AASzH,MAAM,UAAU,YAAY,CAAC,YAAsC,EAAE,OAAoC;IACvG,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IAChD,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IAEjH,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvF,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,wCAAwC,CAAC,CAAC;QAC5G,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,sCAAsC,CAAC,CAAC;IACxG,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAEjC,OAAO;QACL,OAAO;QACP,MAAM,CAAC,OAAO;YACZ,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACnC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtB,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,yDAAyD,CAAC,CAAC;gBACrG,CAAC;YACH,CAAC;YACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gHAAgH;AAChH,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAc;IAC5C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,YAAY,UAAU;QAAE,OAAO,KAAK,CAAC;IACrI,MAAM,IAAI,KAAK,CAAC,0CAA0C,OAAO,KAAK,UAAU,IAAI,2CAA2C,CAAC,CAAC;AACnI,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { SqliteDatabase } from './database.js';
2
+ export declare const SCHEMA_VERSION = 1;
3
+ export declare function initializeSchema(db: SqliteDatabase): void;
4
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,GAAG,IAAI,CA8BzD"}
package/dist/schema.js ADDED
@@ -0,0 +1,35 @@
1
+ export const SCHEMA_VERSION = 1;
2
+ export function initializeSchema(db) {
3
+ db.exec('PRAGMA foreign_keys = ON');
4
+ db.exec(`
5
+ CREATE TABLE IF NOT EXISTS chatcops_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
6
+ CREATE TABLE IF NOT EXISTS chatcops_conversations (
7
+ id TEXT PRIMARY KEY, metadata TEXT, version INTEGER NOT NULL,
8
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
9
+ );
10
+ CREATE TABLE IF NOT EXISTS chatcops_messages (
11
+ conversation_id TEXT NOT NULL, seq INTEGER NOT NULL, id TEXT NOT NULL,
12
+ message TEXT NOT NULL, PRIMARY KEY (conversation_id, seq),
13
+ UNIQUE (conversation_id, id),
14
+ FOREIGN KEY (conversation_id) REFERENCES chatcops_conversations(id) ON DELETE CASCADE
15
+ );
16
+ CREATE TABLE IF NOT EXISTS chatcops_counters (
17
+ key TEXT PRIMARY KEY, value REAL NOT NULL, expires_at INTEGER NOT NULL
18
+ );
19
+ CREATE TABLE IF NOT EXISTS chatcops_kv (
20
+ key TEXT PRIMARY KEY, value TEXT NOT NULL, expires_at INTEGER
21
+ );
22
+ CREATE TABLE IF NOT EXISTS chatcops_events (
23
+ id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, data TEXT, created_at INTEGER NOT NULL
24
+ );
25
+ CREATE INDEX IF NOT EXISTS chatcops_conversations_updated_idx ON chatcops_conversations(updated_at DESC, id);
26
+ CREATE INDEX IF NOT EXISTS chatcops_events_created_idx ON chatcops_events(created_at);
27
+ CREATE INDEX IF NOT EXISTS chatcops_events_type_created_idx ON chatcops_events(type, created_at);
28
+ `);
29
+ const row = db.prepare('SELECT value FROM chatcops_meta WHERE key = ?').get('schema_version');
30
+ if (!row)
31
+ db.prepare('INSERT INTO chatcops_meta (key, value) VALUES (?, ?)').run('schema_version', String(SCHEMA_VERSION));
32
+ else if (Number(row.value) > SCHEMA_VERSION)
33
+ throw new Error(`Unsupported ChatCops SQLite schema version ${row.value}`);
34
+ }
35
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,UAAU,gBAAgB,CAAC,EAAkB;IACjD,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;GAwBP,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAmC,CAAC;IAChI,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;SACtH,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,cAAc;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;AAC1H,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type KeywordQuery, type ScoredRecord, type VectorQuery, type VectorRecord, type VectorStore } from '@chatcops/core';
2
+ import { type SqliteDatabase } from './database.js';
3
+ export declare class SqliteVectors implements VectorStore {
4
+ private readonly db;
5
+ readonly dimensions: number;
6
+ constructor(db: SqliteDatabase, dimensions: number);
7
+ private check;
8
+ upsert(records: VectorRecord[]): Promise<void>;
9
+ query({ vector, topK, filter }: VectorQuery): Promise<ScoredRecord[]>;
10
+ keywordQuery({ text, topK, filter }: KeywordQuery): Promise<ScoredRecord[]>;
11
+ deleteBySource(sourceId: string): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=vectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectors.d.ts","sourceRoot":"","sources":["../src/vectors.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,YAAY,EAAuB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChK,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAwBhF,qBAAa,aAAc,YAAW,WAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,EAAE;IAAkB,QAAQ,CAAC,UAAU,EAAE,MAAM;gBAA/C,EAAE,EAAE,cAAc,EAAW,UAAU,EAAE,MAAM;IAkB5E,OAAO,CAAC,KAAK;IAIP,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9C,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAWrE,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAW3E,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAStD"}
@@ -0,0 +1,100 @@
1
+ import { createRequire } from 'node:module';
2
+ import { segmentWords } from '@chatcops/core';
3
+ import { all, get, run, transaction } from './database.js';
4
+ /** Metadata predicates are evaluated before ranking; keys and values are bound. */
5
+ function predicate(filter) {
6
+ const params = [];
7
+ const clauses = Object.entries(filter ?? {}).map(([key, wanted]) => {
8
+ const values = Array.isArray(wanted) ? wanted : [wanted];
9
+ if (!values.length)
10
+ return '0';
11
+ const alternatives = values.map((value) => {
12
+ params.push(key);
13
+ if (typeof value === 'boolean') {
14
+ params.push(value ? 'true' : 'false');
15
+ return '(m.key = ? AND m.type = ?)';
16
+ }
17
+ params.push(value);
18
+ return `(m.key = ? AND m.type ${typeof value === 'number' ? "IN ('integer', 'real')" : "= 'text'"} AND m.value = ?)`;
19
+ });
20
+ return `EXISTS (SELECT 1 FROM json_each(json_extract(r.record, '$.metadata')) m WHERE ${alternatives.join(' OR ')})`;
21
+ });
22
+ return { sql: clauses.length ? clauses.join(' AND ') : '1', params };
23
+ }
24
+ export class SqliteVectors {
25
+ db;
26
+ dimensions;
27
+ constructor(db, dimensions) {
28
+ this.db = db;
29
+ this.dimensions = dimensions;
30
+ if (!Number.isSafeInteger(dimensions) || dimensions < 1)
31
+ throw new Error('Vector dimensions must be a positive integer');
32
+ const require = createRequire(import.meta.url);
33
+ const { getLoadablePath } = require('sqlite-vec');
34
+ if (!db.loadExtension)
35
+ throw new Error('Vectors require a database opened with allowExtension: true and loadExtension support');
36
+ db.loadExtension(getLoadablePath());
37
+ db.exec(`
38
+ CREATE TABLE IF NOT EXISTS chatcops_vector_config (dimensions INTEGER NOT NULL);
39
+ CREATE TABLE IF NOT EXISTS chatcops_vector_records (rowid INTEGER PRIMARY KEY, id TEXT UNIQUE NOT NULL, source_id TEXT NOT NULL, record TEXT NOT NULL);
40
+ CREATE INDEX IF NOT EXISTS chatcops_vector_source ON chatcops_vector_records(source_id);
41
+ CREATE VIRTUAL TABLE IF NOT EXISTS chatcops_vectors USING vec0(embedding float[${dimensions}] distance_metric=cosine);
42
+ CREATE VIRTUAL TABLE IF NOT EXISTS chatcops_vector_fts USING fts5(words);
43
+ `);
44
+ const config = get(db, 'SELECT dimensions FROM chatcops_vector_config');
45
+ if (config && config.dimensions !== dimensions)
46
+ throw new Error(`SQLite vectors have ${config.dimensions} dimensions, requested ${dimensions}`);
47
+ if (!config)
48
+ run(db, 'INSERT INTO chatcops_vector_config VALUES (?)', dimensions);
49
+ }
50
+ check(vector) {
51
+ if (vector.length !== this.dimensions || !vector.every(Number.isFinite))
52
+ throw new Error(`Expected ${this.dimensions} finite vector dimensions`);
53
+ }
54
+ async upsert(records) {
55
+ for (const record of records)
56
+ this.check(record.vector);
57
+ transaction(this.db, () => {
58
+ for (const { vector, ...record } of records) {
59
+ const row = get(this.db, 'INSERT INTO chatcops_vector_records (id, source_id, record) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET source_id=excluded.source_id, record=excluded.record RETURNING CAST(rowid AS TEXT) AS rowid', record.id, record.source.id, JSON.stringify(record));
60
+ if (!row)
61
+ throw new Error('Vector upsert returned no row');
62
+ const rowid = BigInt(row.rowid);
63
+ run(this.db, 'DELETE FROM chatcops_vectors WHERE rowid = ?', rowid);
64
+ run(this.db, 'INSERT INTO chatcops_vectors(rowid, embedding) VALUES (?, ?)', rowid, JSON.stringify(Array.from(vector)));
65
+ run(this.db, 'DELETE FROM chatcops_vector_fts WHERE rowid = ?', rowid);
66
+ run(this.db, 'INSERT INTO chatcops_vector_fts(rowid, words) VALUES (?, ?)', rowid, segmentWords(record.text).join(' '));
67
+ }
68
+ });
69
+ }
70
+ async query({ vector, topK, filter }) {
71
+ this.check(vector);
72
+ if (!(topK >= 1))
73
+ return [];
74
+ const where = predicate(filter);
75
+ const rows = all(this.db, `SELECT r.record, CASE WHEN vec_distance_cosine(v.embedding, ?) IS NULL THEN 0 ELSE 1 - vec_distance_cosine(v.embedding, ?) END AS score
76
+ FROM chatcops_vector_records r JOIN chatcops_vectors v ON v.rowid=r.rowid
77
+ WHERE ${where.sql} ORDER BY score DESC, r.id LIMIT ?`, JSON.stringify(Array.from(vector)), JSON.stringify(Array.from(vector)), ...where.params, Math.floor(topK));
78
+ return rows.map((row) => ({ ...JSON.parse(row.record), score: row.score }));
79
+ }
80
+ async keywordQuery({ text, topK, filter }) {
81
+ const words = [...new Set(segmentWords(text))];
82
+ if (!words.length || !(topK >= 1))
83
+ return [];
84
+ const where = predicate(filter);
85
+ const rows = all(this.db, `SELECT r.record, -bm25(chatcops_vector_fts) AS score
86
+ FROM chatcops_vector_fts JOIN chatcops_vector_records r ON r.rowid=chatcops_vector_fts.rowid
87
+ WHERE chatcops_vector_fts MATCH ? AND ${where.sql} ORDER BY score DESC, r.id LIMIT ?`, words.map((word) => `"${word.replaceAll('"', '""')}"`).join(' OR '), ...where.params, Math.floor(topK));
88
+ return rows.map((row) => ({ ...JSON.parse(row.record), score: row.score }));
89
+ }
90
+ async deleteBySource(sourceId) {
91
+ transaction(this.db, () => {
92
+ for (const row of all(this.db, 'SELECT CAST(rowid AS TEXT) AS rowid FROM chatcops_vector_records WHERE source_id = ?', sourceId)) {
93
+ run(this.db, 'DELETE FROM chatcops_vectors WHERE rowid = ?', BigInt(row.rowid));
94
+ run(this.db, 'DELETE FROM chatcops_vector_fts WHERE rowid = ?', row.rowid);
95
+ }
96
+ run(this.db, 'DELETE FROM chatcops_vector_records WHERE source_id = ?', sourceId);
97
+ });
98
+ }
99
+ }
100
+ //# sourceMappingURL=vectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectors.js","sourceRoot":"","sources":["../src/vectors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAoH,MAAM,gBAAgB,CAAC;AAChK,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAuB,MAAM,eAAe,CAAC;AAIhF,mFAAmF;AACnF,SAAS,SAAS,CAAC,MAAuB;IACxC,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE;QACjE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC;QAC/B,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACxC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjB,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACtC,OAAO,4BAA4B,CAAC;YACtC,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,yBAAyB,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,UAAU,mBAAmB,CAAC;QACvH,CAAC,CAAC,CAAC;QACH,OAAO,iFAAiF,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;IACvH,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC;AACvE,CAAC;AAED,MAAM,OAAO,aAAa;IACK;IAA6B;IAA1D,YAA6B,EAAkB,EAAW,UAAkB;QAA/C,OAAE,GAAF,EAAE,CAAgB;QAAW,eAAU,GAAV,UAAU,CAAQ;QAC1E,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACzH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,YAAY,CAAkC,CAAC;QACnF,IAAI,CAAC,EAAE,CAAC,aAAa;YAAE,MAAM,IAAI,KAAK,CAAC,uFAAuF,CAAC,CAAC;QAChI,EAAE,CAAC,aAAa,CAAC,eAAe,EAAE,CAAC,CAAC;QACpC,EAAE,CAAC,IAAI,CAAC;;;;uFAI2E,UAAU;;KAE5F,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,CAAyB,EAAE,EAAE,+CAA+C,CAAC,CAAC;QAChG,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,KAAK,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,CAAC,UAAU,0BAA0B,UAAU,EAAE,CAAC,CAAC;QAChJ,IAAI,CAAC,MAAM;YAAE,GAAG,CAAC,EAAE,EAAE,+CAA+C,EAAE,UAAU,CAAC,CAAC;IACpF,CAAC;IAEO,KAAK,CAAC,MAAoB;QAChC,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,UAAU,2BAA2B,CAAC,CAAC;IACnJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAuB;QAClC,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxD,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE;YACxB,KAAK,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;gBAC5C,MAAM,GAAG,GAAG,GAAG,CAAoB,IAAI,CAAC,EAAE,EACxC,wMAAwM,EACxM,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBACvD,IAAI,CAAC,GAAG;oBAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;gBAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAChC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,8CAA8C,EAAE,KAAK,CAAC,CAAC;gBACpE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,8DAA8D,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACxH,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,iDAAiD,EAAE,KAAK,CAAC,CAAC;gBACvE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,6DAA6D,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1H,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAe;QAC/C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnB,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,GAAG,CAAM,IAAI,CAAC,EAAE,EAAE;;cAErB,KAAK,CAAC,GAAG,oCAAoC,EACrD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7G,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAgB;QACrD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,GAAG,CAAM,IAAI,CAAC,EAAE,EAAE;;8CAEW,KAAK,CAAC,GAAG,oCAAoC,EACrF,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1G,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,QAAgB;QACnC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE;YACxB,KAAK,MAAM,GAAG,IAAI,GAAG,CAAM,IAAI,CAAC,EAAE,EAAE,sFAAsF,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACtI,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,8CAA8C,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;gBAChF,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,iDAAiD,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YAC7E,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,yDAAyD,EAAE,QAAQ,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@chatcops/store-sqlite",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "SQLite storage for ChatCops single-server deployments",
5
+ "keywords": [
6
+ "chatcops",
7
+ "storage",
8
+ "sqlite"
9
+ ],
10
+ "homepage": "https://chat.codercops.com",
11
+ "bugs": "https://github.com/codercops/chatcops/issues",
12
+ "type": "module",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "peerDependencies": {
26
+ "sqlite-vec": "^0.1.9",
27
+ "@chatcops/core": "^0.6.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "sqlite-vec": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22",
36
+ "sqlite-vec": "^0.1.9",
37
+ "typescript": "^5.7.0",
38
+ "vitest": "^4.1.11",
39
+ "@chatcops/core": "0.6.0"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/codercops/chatcops.git",
44
+ "directory": "packages/store-sqlite"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "license": "MIT",
50
+ "engines": {
51
+ "node": ">=22.13"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc",
55
+ "typecheck": "tsc -p tsconfig.test.json --noEmit",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "clean": "rm -rf dist *.tsbuildinfo"
59
+ }
6
60
  }