@wirecat/cli-core 0.432.0 → 0.434.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 +25 -0
- package/dist/cli/messenger/store-maintenance-command.js +5 -2
- package/dist/cli/upgrade-command.js +1 -1
- package/dist/entities/index.d.ts +10 -3
- package/dist/entities/store-maintenance.d.ts +10 -3
- package/dist/entities/store-maintenance.js +10 -1
- package/dist/errors/reasons.js +7 -0
- package/dist/models/anthropic.js +2 -4
- package/dist/store/drivers/bun-sqlite.d.ts +6 -0
- package/dist/store/drivers/bun-sqlite.js +16 -1
- package/dist/store/drivers/node-sqlite.d.ts +5 -1
- package/dist/store/drivers/node-sqlite.js +16 -1
- package/dist/store/exports.d.ts +1 -0
- package/dist/store/exports.js +1 -0
- package/dist/store/sqlite/document-pictures.js +4 -1
- package/dist/store/sqlite/drizzle/bun.js +58 -14
- package/dist/store/sqlite/drizzle/bun.js.map +3 -3
- package/dist/store/sqlite/drizzle/core.js +173 -7
- package/dist/store/sqlite/drizzle/core.js.map +4 -4
- package/dist/store/sqlite/drizzle/node.js +57 -13
- package/dist/store/sqlite/drizzle/node.js.map +3 -3
- package/dist/store/sqlite/drizzle/shared-55XJMTPA.js +6514 -0
- package/dist/store/sqlite/drizzle/shared-55XJMTPA.js.map +7 -0
- package/dist/store/sqlite/image-chunks.d.ts +1 -1
- package/dist/store/sqlite/image-chunks.js +46 -13
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/message-chunks.d.ts +5 -2
- package/dist/store/sqlite/message-chunks.js +42 -13
- package/dist/store/sqlite/migrations.generated.js +22 -0
- package/dist/store/sqlite/open-store.js +4 -0
- package/dist/store/sqlite/open.js +5 -6
- package/dist/store/sqlite/schema/canonical-tags.d.ts +1 -0
- package/dist/store/sqlite/schema/document-index-pending.d.ts +1 -0
- package/dist/store/sqlite/schema/document-transfers.d.ts +1 -0
- package/dist/store/sqlite/schema/tag-consolidations.d.ts +2 -0
- package/dist/store/sqlite/schema.d.ts +214 -0
- package/dist/store/sqlite/schema.js +17 -0
- package/dist/store/sqlite/vector-index.d.ts +67 -0
- package/dist/store/sqlite/vector-index.js +180 -0
- package/dist/store/sqlite/vectors.d.ts +6 -3
- package/dist/store/sqlite/vectors.js +71 -31
- package/dist/store/vector-extension.d.ts +10 -0
- package/dist/store/vector-extension.js +53 -0
- package/package.json +24 -25
- package/dist/store/sqlite/drizzle/shared-HKXF6HKX.js +0 -4846
- package/dist/store/sqlite/drizzle/shared-HKXF6HKX.js.map +0 -7
|
@@ -46,7 +46,7 @@ export interface NearestImage {
|
|
|
46
46
|
* The images of the account nearest to `query` by the vector of `model`, best first. Position 0 of an image is
|
|
47
47
|
* its context text, position 1 its picture (`PICTURE_POSITION`): a model's vectors only ever sit at one.
|
|
48
48
|
*/
|
|
49
|
-
export declare const nearestImages: (
|
|
49
|
+
export declare const nearestImages: (context: StoreContext, accountPk: number, { chatKey, model, since, limit, query, position, }: {
|
|
50
50
|
chatKey?: number;
|
|
51
51
|
model: string;
|
|
52
52
|
since?: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sql } from "./drizzle/core.js";
|
|
2
|
+
import { searchIndex } from "./vector-index.js";
|
|
2
3
|
import { dot } from "./vectors.js";
|
|
3
4
|
const IS_IMAGE = sql `(a.kind = 'photo' OR a.mime LIKE 'image/%')`;
|
|
4
5
|
const neighbours = ({ orm }, chatKey, messagePk, sentAt, { messages, withinMs }, side) => orm.all(sql `SELECT sender_name AS sender, text, sent_at AS sentAt FROM messages
|
|
@@ -62,32 +63,64 @@ export const embeddedHashes = ({ orm }, model, hashes) => new Set(hashes.length
|
|
|
62
63
|
WHERE model = ${model} AND content_hash IN (SELECT value FROM json_each(${JSON.stringify(hashes)}))`)
|
|
63
64
|
.map(({ hash }) => hash));
|
|
64
65
|
const SCAN_PAGE = 5_000;
|
|
66
|
+
/** The scan's rows: one page after `after`, or the `ids` the index picked. */
|
|
67
|
+
const imageRows = ({ orm }, accountPk, { chatKey, model, since, position }, rows) => orm.all(sql `SELECT k.id, k.content_hash AS hash, ch.external_id AS chatId, m.external_id AS messageId, v.vector
|
|
68
|
+
FROM chunks k CROSS JOIN attachments a ON a.id = k.chunkable_id AND a.attachable_type = 'message'
|
|
69
|
+
JOIN messages m ON m.id = a.attachable_id AND m.deleted_at IS NULL
|
|
70
|
+
JOIN chats ch ON ch.id = m.chat_id
|
|
71
|
+
JOIN embeddings v ON v.model = ${model} AND v.content_hash = k.content_hash
|
|
72
|
+
WHERE k.chunkable_type = 'image' AND k.position = ${position} AND ch.account_id = ${accountPk}
|
|
73
|
+
${chatKey === undefined ? sql `AND ch.searchable = 1` : sql `AND m.chat_id = ${chatKey}`}
|
|
74
|
+
${since === undefined ? sql `` : sql `AND k.occurred_at >= ${since}`}
|
|
75
|
+
${"ids" in rows
|
|
76
|
+
? sql `AND k.id IN (SELECT value FROM json_each(${JSON.stringify(rows.ids)})) ORDER BY k.id`
|
|
77
|
+
: sql `AND k.id > ${rows.after} ORDER BY k.id LIMIT ${SCAN_PAGE}`}`);
|
|
65
78
|
/**
|
|
66
79
|
* The images of the account nearest to `query` by the vector of `model`, best first. Position 0 of an image is
|
|
67
80
|
* its context text, position 1 its picture (`PICTURE_POSITION`): a model's vectors only ever sit at one.
|
|
68
81
|
*/
|
|
69
|
-
export const nearestImages = (
|
|
82
|
+
export const nearestImages = (context, accountPk, { chatKey, model, since, limit, query, position = 0, }) => {
|
|
83
|
+
const filter = {
|
|
84
|
+
model,
|
|
85
|
+
position,
|
|
86
|
+
...(chatKey === undefined ? {} : { chatKey }),
|
|
87
|
+
...(since === undefined ? {} : { since }),
|
|
88
|
+
};
|
|
89
|
+
const score = (row) => ({
|
|
90
|
+
id: row.id,
|
|
91
|
+
chatId: row.chatId,
|
|
92
|
+
messageId: row.messageId,
|
|
93
|
+
score: dot(query, row.vector),
|
|
94
|
+
});
|
|
95
|
+
// Stable, so equal scores keep the scan's order, by chunk id.
|
|
96
|
+
const best = (found) => found.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
97
|
+
const indexed = searchIndex(context.database, model, {
|
|
98
|
+
kind: "image",
|
|
99
|
+
account: accountPk,
|
|
100
|
+
...(chatKey === undefined ? {} : { chat: chatKey }),
|
|
101
|
+
...(since === undefined ? {} : { since }),
|
|
102
|
+
}, query, limit, (candidates) => {
|
|
103
|
+
const hashes = new Map(candidates.map(({ id, hash }) => [id, hash]));
|
|
104
|
+
const results = best(imageRows(context, accountPk, filter, { ids: [...hashes.keys()] })
|
|
105
|
+
.filter((row) => hashes.get(row.id) === row.hash)
|
|
106
|
+
.map(score));
|
|
107
|
+
return { results, best: results.map(({ id }) => id) };
|
|
108
|
+
});
|
|
109
|
+
const plain = (found) => found.map(({ chatId, messageId, score }) => ({ chatId, messageId, score }));
|
|
110
|
+
if (indexed)
|
|
111
|
+
return plain(indexed);
|
|
70
112
|
const found = [];
|
|
71
113
|
let after = 0;
|
|
72
114
|
for (;;) {
|
|
73
|
-
const rows =
|
|
74
|
-
FROM chunks k CROSS JOIN attachments a ON a.id = k.chunkable_id AND a.attachable_type = 'message'
|
|
75
|
-
JOIN messages m ON m.id = a.attachable_id AND m.deleted_at IS NULL
|
|
76
|
-
JOIN chats ch ON ch.id = m.chat_id
|
|
77
|
-
JOIN embeddings v ON v.model = ${model} AND v.content_hash = k.content_hash
|
|
78
|
-
WHERE k.chunkable_type = 'image' AND k.position = ${position} AND ch.account_id = ${accountPk}
|
|
79
|
-
AND k.id > ${after}
|
|
80
|
-
${chatKey === undefined ? sql `AND ch.searchable = 1` : sql `AND m.chat_id = ${chatKey}`}
|
|
81
|
-
${since === undefined ? sql `` : sql `AND k.occurred_at >= ${since}`}
|
|
82
|
-
ORDER BY k.id LIMIT ${SCAN_PAGE}`);
|
|
115
|
+
const rows = imageRows(context, accountPk, filter, { after });
|
|
83
116
|
for (const row of rows)
|
|
84
|
-
found.push(
|
|
117
|
+
found.push(score(row));
|
|
85
118
|
const last = rows.at(-1);
|
|
86
119
|
if (!last || rows.length < SCAN_PAGE)
|
|
87
120
|
break;
|
|
88
121
|
after = last.id;
|
|
89
122
|
}
|
|
90
|
-
return
|
|
123
|
+
return plain(best(found));
|
|
91
124
|
};
|
|
92
125
|
/** An image's own vector is its chunk at this position; position 0 is the text around it. */
|
|
93
126
|
export const PICTURE_POSITION = 1;
|
|
@@ -43,6 +43,8 @@ export const MANIFEST = [
|
|
|
43
43
|
{ name: "20261025071850_version-32-files-data", version: 32, minCompatible: 32 },
|
|
44
44
|
{ name: "20261025072643_version-33-calendars", version: 33, minCompatible: 32 },
|
|
45
45
|
{ name: "20261025072644_version-33-calendars-triggers", version: 33, minCompatible: 32 },
|
|
46
|
+
{ name: "20261025182500_version-34-vector-index", version: 34, minCompatible: 34 },
|
|
47
|
+
{ name: "20261025182501_version-34-vector-index-triggers", version: 34, minCompatible: 34 },
|
|
46
48
|
];
|
|
47
49
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
48
50
|
const migrations = [];
|
|
@@ -33,12 +33,15 @@ export interface NearestMessage {
|
|
|
33
33
|
messageId: string;
|
|
34
34
|
score: number;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
export declare const nearestMessages: ({ orm }: StoreContext, accountPk: number, { chatKey, model, since, before, limit, query, }: {
|
|
36
|
+
type MessageFilter = {
|
|
38
37
|
chatKey?: number;
|
|
39
38
|
model: string;
|
|
40
39
|
since?: number;
|
|
41
40
|
before?: number;
|
|
41
|
+
};
|
|
42
|
+
/** The account's live messages nearest to `query` by their vector of `model`, best first. */
|
|
43
|
+
export declare const nearestMessages: (context: StoreContext, accountPk: number, { limit, query, ...filter }: MessageFilter & {
|
|
42
44
|
limit: number;
|
|
43
45
|
query: Float32Array;
|
|
44
46
|
}) => NearestMessage[];
|
|
47
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sql } from "./drizzle/core.js";
|
|
2
|
+
import { searchIndex } from "./vector-index.js";
|
|
2
3
|
import { dot } from "./vectors.js";
|
|
3
4
|
/** Live messages with text of the account, by pk after `after`; the caller compares hashes. */
|
|
4
5
|
export const messagesToEmbed = ({ orm }, accountPk, { chatKey, model, after, limit }) => orm
|
|
@@ -51,26 +52,54 @@ export const dropGoneMessageChunks = (context, accountPk) => {
|
|
|
51
52
|
export const hasMessageVectors = ({ orm }, accountPk, model) => orm.get(sql `SELECT 1 FROM chunks k JOIN embeddings v ON v.model = ${model} AND v.content_hash = k.content_hash
|
|
52
53
|
WHERE k.chunkable_type = 'message' AND k.account_id = ${accountPk} LIMIT 1`) !== undefined;
|
|
53
54
|
const SCAN_PAGE = 5_000;
|
|
54
|
-
/** The
|
|
55
|
-
|
|
55
|
+
/** The scan's rows: one page after `after`, or the `ids` the index picked. */
|
|
56
|
+
const messageRows = ({ orm }, accountPk, { chatKey, model, since, before }, rows) => orm.all(sql `SELECT k.id, k.content_hash AS hash, ch.external_id AS chatId, m.external_id AS messageId, v.vector
|
|
57
|
+
FROM chunks k CROSS JOIN messages m ON m.id = k.chunkable_id AND m.deleted_at IS NULL
|
|
58
|
+
JOIN chats ch ON ch.id = m.chat_id
|
|
59
|
+
JOIN embeddings v ON v.model = ${model} AND v.content_hash = k.content_hash
|
|
60
|
+
WHERE k.chunkable_type = 'message' AND k.position = 0 AND k.account_id = ${accountPk}
|
|
61
|
+
${chatKey === undefined ? sql `AND ch.searchable = 1` : sql `AND m.chat_id = ${chatKey}`}
|
|
62
|
+
${since === undefined ? sql `` : sql `AND m.sent_at >= ${since}`}
|
|
63
|
+
${before === undefined ? sql `` : sql `AND m.sent_at < ${before}`}
|
|
64
|
+
${"ids" in rows
|
|
65
|
+
? sql `AND k.id IN (SELECT value FROM json_each(${JSON.stringify(rows.ids)})) ORDER BY k.id`
|
|
66
|
+
: sql `AND k.id > ${rows.after} ORDER BY k.id LIMIT ${SCAN_PAGE}`}`);
|
|
67
|
+
/** The account's live messages nearest to `query` by their vector of `model`, best first. */
|
|
68
|
+
export const nearestMessages = (context, accountPk, { limit, query, ...filter }) => {
|
|
69
|
+
const score = (row) => ({
|
|
70
|
+
id: row.id,
|
|
71
|
+
chatId: row.chatId,
|
|
72
|
+
messageId: row.messageId,
|
|
73
|
+
score: dot(query, row.vector),
|
|
74
|
+
});
|
|
75
|
+
// Stable, so equal scores keep the scan's order, by chunk id.
|
|
76
|
+
const best = (found) => found.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
77
|
+
const indexed = searchIndex(context.database, filter.model, {
|
|
78
|
+
kind: "message",
|
|
79
|
+
account: accountPk,
|
|
80
|
+
...(filter.chatKey === undefined ? {} : { chat: filter.chatKey }),
|
|
81
|
+
...(filter.since === undefined ? {} : { since: filter.since }),
|
|
82
|
+
...(filter.before === undefined ? {} : { before: filter.before }),
|
|
83
|
+
}, query, limit, (candidates) => {
|
|
84
|
+
const hashes = new Map(candidates.map(({ id, hash }) => [id, hash]));
|
|
85
|
+
const results = best(messageRows(context, accountPk, filter, { ids: [...hashes.keys()] })
|
|
86
|
+
.filter((row) => hashes.get(row.id) === row.hash)
|
|
87
|
+
.map(score));
|
|
88
|
+
return { results, best: results.map(({ id }) => id) };
|
|
89
|
+
});
|
|
90
|
+
const plain = (found) => found.map(({ chatId, messageId, score }) => ({ chatId, messageId, score }));
|
|
91
|
+
if (indexed)
|
|
92
|
+
return plain(indexed);
|
|
56
93
|
const found = [];
|
|
57
94
|
let after = 0;
|
|
58
95
|
for (;;) {
|
|
59
|
-
const rows =
|
|
60
|
-
FROM chunks k CROSS JOIN messages m ON m.id = k.chunkable_id AND m.deleted_at IS NULL
|
|
61
|
-
JOIN chats ch ON ch.id = m.chat_id
|
|
62
|
-
JOIN embeddings v ON v.model = ${model} AND v.content_hash = k.content_hash
|
|
63
|
-
WHERE k.chunkable_type = 'message' AND k.position = 0 AND k.account_id = ${accountPk} AND k.id > ${after}
|
|
64
|
-
${chatKey === undefined ? sql `AND ch.searchable = 1` : sql `AND m.chat_id = ${chatKey}`}
|
|
65
|
-
${since === undefined ? sql `` : sql `AND m.sent_at >= ${since}`}
|
|
66
|
-
${before === undefined ? sql `` : sql `AND m.sent_at < ${before}`}
|
|
67
|
-
ORDER BY k.id LIMIT ${SCAN_PAGE}`);
|
|
96
|
+
const rows = messageRows(context, accountPk, filter, { after });
|
|
68
97
|
for (const row of rows)
|
|
69
|
-
found.push(
|
|
98
|
+
found.push(score(row));
|
|
70
99
|
const last = rows.at(-1);
|
|
71
100
|
if (!last || rows.length < SCAN_PAGE)
|
|
72
101
|
break;
|
|
73
102
|
after = last.id;
|
|
74
103
|
}
|
|
75
|
-
return
|
|
104
|
+
return plain(best(found));
|
|
76
105
|
};
|
|
@@ -739,5 +739,27 @@ export const GENERATED = [
|
|
|
739
739
|
"INSERT OR IGNORE INTO search_index_state (name, watermark, filled_through, terms_through, normalizer_version, built_at, analyzer)\n VALUES ('event_index', 0, 0, 0, 1, NULL, NULL);",
|
|
740
740
|
"INSERT OR IGNORE INTO event_index_pending (indexable_type, id) SELECT 'event', id FROM events;"
|
|
741
741
|
]
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
"name": "20261025182500_version-34-vector-index",
|
|
745
|
+
"statements": [
|
|
746
|
+
"CREATE TABLE `vector_index_redo` (\n\t`chunk_id` integer NOT NULL\n);",
|
|
747
|
+
"CREATE TABLE `vector_indexes` (\n\t`id` integer PRIMARY KEY,\n\t`model` text NOT NULL UNIQUE,\n\t`dims` integer NOT NULL,\n\t`ready` integer NOT NULL,\n\t`filled_through` text NOT NULL\n);"
|
|
748
|
+
]
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
"name": "20261025182501_version-34-vector-index-triggers",
|
|
752
|
+
"statements": [
|
|
753
|
+
"-- Every change that can add, drop or move a chunk's vector passes the chunk's id through vector_index_redo, where\n-- each model's own trigger (made with its vec0 table, `vector-index.ts`) re-derives that row. Writes in TypeScript\n-- and the schema's own delete triggers (document_bd, note_bd, …) all reach the index this way.\n-- A chunk usually comes before its vector; the vector's own trigger indexes it then.\n-- CROSS JOIN: led by the vectors, SQLite read every one of them per chunk (7 ms each at 100k).\nCREATE TRIGGER vector_index_chunk_ai AFTER INSERT ON chunks WHEN EXISTS (SELECT 1 FROM vector_indexes i\n CROSS JOIN embeddings v ON v.model = i.model AND v.content_hash = new.content_hash) BEGIN\n INSERT INTO vector_index_redo (chunk_id) VALUES (new.id);\n DELETE FROM vector_index_redo;\nEND;",
|
|
754
|
+
"CREATE TRIGGER vector_index_chunk_ad AFTER DELETE ON chunks BEGIN\n INSERT INTO vector_index_redo (chunk_id) VALUES (old.id);\n DELETE FROM vector_index_redo;\nEND;",
|
|
755
|
+
"CREATE TRIGGER vector_index_chunk_au AFTER UPDATE OF content_hash, chunkable_type, chunkable_id, account_id, occurred_at\n ON chunks WHEN old.content_hash IS NOT new.content_hash OR old.chunkable_type IS NOT new.chunkable_type\n OR old.chunkable_id IS NOT new.chunkable_id OR old.account_id IS NOT new.account_id\n OR old.occurred_at IS NOT new.occurred_at BEGIN\n INSERT INTO vector_index_redo (chunk_id) VALUES (new.id);\n DELETE FROM vector_index_redo;\nEND;",
|
|
756
|
+
"CREATE TRIGGER vector_index_embedding_ai AFTER INSERT ON embeddings\n WHEN EXISTS (SELECT 1 FROM vector_indexes WHERE model = new.model) BEGIN\n INSERT INTO vector_index_redo (chunk_id) SELECT id FROM chunks WHERE content_hash = new.content_hash;\n DELETE FROM vector_index_redo;\nEND;",
|
|
757
|
+
"CREATE TRIGGER vector_index_embedding_ad AFTER DELETE ON embeddings\n WHEN EXISTS (SELECT 1 FROM vector_indexes WHERE model = old.model) BEGIN\n INSERT INTO vector_index_redo (chunk_id) SELECT id FROM chunks WHERE content_hash = old.content_hash;\n DELETE FROM vector_index_redo;\nEND;",
|
|
758
|
+
"CREATE TRIGGER vector_index_embedding_au AFTER UPDATE OF model, content_hash, vector ON embeddings BEGIN\n INSERT INTO vector_index_redo (chunk_id)\n SELECT id FROM chunks WHERE content_hash = old.content_hash OR content_hash = new.content_hash;\n DELETE FROM vector_index_redo;\nEND;",
|
|
759
|
+
"-- A message's chunk and its images' chunks are indexed under its chat and send time; a chat merge moves messages.\nCREATE TRIGGER vector_index_message_au AFTER UPDATE OF chat_id, sent_at ON messages\n WHEN old.chat_id IS NOT new.chat_id OR old.sent_at IS NOT new.sent_at BEGIN\n INSERT INTO vector_index_redo (chunk_id)\n SELECT id FROM chunks WHERE chunkable_type = 'message' AND chunkable_id = new.id\n UNION ALL SELECT k.id FROM attachments a JOIN chunks k ON k.chunkable_type = 'image' AND k.chunkable_id = a.id\n WHERE a.attachable_type = 'message' AND a.attachable_id = new.id;\n DELETE FROM vector_index_redo;\nEND;",
|
|
760
|
+
"-- An image's chat and account come from its message; a chat merge moves attachments to the kept copy.\nCREATE TRIGGER vector_index_attachment_au AFTER UPDATE OF attachable_type, attachable_id ON attachments\n WHEN old.attachable_type IS NOT new.attachable_type OR old.attachable_id IS NOT new.attachable_id BEGIN\n INSERT INTO vector_index_redo (chunk_id) SELECT id FROM chunks WHERE chunkable_type = 'image' AND chunkable_id = new.id;\n DELETE FROM vector_index_redo;\nEND;",
|
|
761
|
+
"CREATE TRIGGER vector_index_conversation_au AFTER UPDATE OF chat_id ON conversations\n WHEN old.chat_id IS NOT new.chat_id BEGIN\n INSERT INTO vector_index_redo (chunk_id)\n SELECT id FROM chunks WHERE chunkable_type = 'conversation' AND chunkable_id = new.id;\n DELETE FROM vector_index_redo;\nEND;",
|
|
762
|
+
"-- Images and conversations are indexed under their chat's account; messages carry their own.\nCREATE TRIGGER vector_index_chat_au AFTER UPDATE OF account_id ON chats WHEN old.account_id IS NOT new.account_id BEGIN\n INSERT INTO vector_index_redo (chunk_id)\n SELECT k.id FROM messages m JOIN attachments a ON a.attachable_type = 'message' AND a.attachable_id = m.id\n JOIN chunks k ON k.chunkable_type = 'image' AND k.chunkable_id = a.id WHERE m.chat_id = new.id\n UNION ALL SELECT k.id FROM conversations c JOIN chunks k ON k.chunkable_type = 'conversation' AND k.chunkable_id = c.id\n WHERE c.chat_id = new.id;\n DELETE FROM vector_index_redo;\nEND;"
|
|
763
|
+
]
|
|
742
764
|
}
|
|
743
765
|
];
|
|
@@ -15,6 +15,7 @@ import { loadProviders } from "./providers.js";
|
|
|
15
15
|
import { assertReadCompatible } from "./read-compatibility.js";
|
|
16
16
|
import { stemsState } from "./stems.js";
|
|
17
17
|
import { fillStems, followDefaultStemmers } from "./stems-fill.js";
|
|
18
|
+
import { fillVectorIndexes } from "./vector-index.js";
|
|
18
19
|
export const BACKFILL_ON_OPEN = 5_000;
|
|
19
20
|
const NOT_WRITABLE = new Set(["EACCES", "EPERM", "EROFS"]);
|
|
20
21
|
const createWritable = (file) => {
|
|
@@ -62,6 +63,9 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
|
|
|
62
63
|
stemming.watermark - stemming.filledThrough + stemming.pending <= BACKFILL_ON_OPEN)
|
|
63
64
|
fillStems(database, { now });
|
|
64
65
|
logPruner(database, now)();
|
|
66
|
+
fillVectorIndexes(database, {
|
|
67
|
+
notice: (model) => warn(`indexing the stored vectors of ${model} for search by meaning — once; a large archive takes minutes`),
|
|
68
|
+
});
|
|
65
69
|
}
|
|
66
70
|
loadProviders(database);
|
|
67
71
|
return { ...opened, now, sharedConfig: sharedConfigPath(file, env ?? process.env) };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all lint/plugin/no-sqlite-driver: cli-core owns the store, so it opens the driver itself
|
|
1
2
|
import { PRAGMAS } from "../driver.js";
|
|
2
3
|
import { quietSqliteWarning } from "../sqlite-warning.js";
|
|
3
4
|
/**
|
|
@@ -19,10 +20,9 @@ export const openSqlite = async (path, { readOnly = false } = {}) => {
|
|
|
19
20
|
};
|
|
20
21
|
const openUnderNode = async (path, readOnly) => {
|
|
21
22
|
quietSqliteWarning();
|
|
22
|
-
const { DatabaseSync } = await import("node:sqlite");
|
|
23
23
|
const { drizzle } = await import("./drizzle/node.js");
|
|
24
|
-
const { cacheOverNodeSqlite } = await import("../drivers/node-sqlite.js");
|
|
25
|
-
const client =
|
|
24
|
+
const { cacheOverNodeSqlite, nodeClient } = await import("../drivers/node-sqlite.js");
|
|
25
|
+
const client = await nodeClient(path, { readOnly });
|
|
26
26
|
try {
|
|
27
27
|
return { database: cacheOverNodeSqlite(client), orm: drizzle({ client }) };
|
|
28
28
|
}
|
|
@@ -32,10 +32,9 @@ const openUnderNode = async (path, readOnly) => {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
const openUnderBun = async (path, readOnly) => {
|
|
35
|
-
const {
|
|
36
|
-
const Database = await bunDatabase();
|
|
35
|
+
const { bunClient, cacheOverBunSqlite } = await import("../drivers/bun-sqlite.js");
|
|
37
36
|
const { drizzle } = (await import("./drizzle/bun.js"));
|
|
38
|
-
const client =
|
|
37
|
+
const client = await bunClient(path, readOnly ? { readonly: true, create: false } : undefined);
|
|
39
38
|
try {
|
|
40
39
|
return { database: cacheOverBunSqlite(client), orm: drizzle({ client }) };
|
|
41
40
|
}
|
|
@@ -52,6 +52,7 @@ export declare const tagRedirects: import("drizzle-orm/sqlite-core").SQLiteTable
|
|
|
52
52
|
}, {}>;
|
|
53
53
|
};
|
|
54
54
|
dialect: 'sqlite';
|
|
55
|
+
isAlias: false;
|
|
55
56
|
}>;
|
|
56
57
|
export declare const tagConsolidations: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
57
58
|
name: "tag_consolidations";
|
|
@@ -267,4 +268,5 @@ export declare const tagConsolidations: import("drizzle-orm/sqlite-core").SQLite
|
|
|
267
268
|
}, {}>;
|
|
268
269
|
};
|
|
269
270
|
dialect: 'sqlite';
|
|
271
|
+
isAlias: false;
|
|
270
272
|
}>;
|