@wirecat/cli-core 0.593.0 → 0.595.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 +51 -0
- package/dist/background/schedules.d.ts +10 -1
- package/dist/background/schedules.js +42 -5
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/messenger/store-maintenance-command.js +54 -5
- package/dist/cli/schedule/schedule-command.d.ts +4 -2
- package/dist/cli/schedule/schedule-command.js +3 -1
- package/dist/crm/cli/index.d.ts +6 -0
- package/dist/crm/cli/index.js +118 -2
- package/dist/crm/index.d.ts +2 -0
- package/dist/crm/index.js +2 -0
- package/dist/crm/testing/index.d.ts +2 -1
- package/dist/crm/testing/index.js +2 -1
- package/dist/crm/testing/source-contract.js +2 -2
- package/dist/crm/testing/source.d.ts +22 -2
- package/dist/crm/testing/source.js +117 -1
- package/dist/crm/testing/sync-contract.js +3 -2
- package/dist/crm/testing/write-contract.d.ts +21 -0
- package/dist/crm/testing/write-contract.js +127 -0
- package/dist/crm/writer.d.ts +76 -0
- package/dist/crm/writer.js +14 -0
- package/dist/crm/writes.d.ts +68 -0
- package/dist/crm/writes.js +245 -0
- package/dist/domain/crm.d.ts +3 -4
- package/dist/entities/index.d.ts +14 -2
- package/dist/entities/store-maintenance.d.ts +14 -2
- package/dist/entities/store-maintenance.js +19 -6
- package/dist/errors/reasons.js +2 -1
- package/dist/mail/cli/review.js +11 -4
- package/dist/mail/marks.d.ts +2 -0
- package/dist/mail/marks.js +8 -6
- package/dist/mail/newsletter.d.ts +10 -0
- package/dist/mail/newsletter.js +70 -0
- package/dist/mail/review.d.ts +1 -0
- package/dist/store/migrations.d.ts +11 -2
- package/dist/store/migrations.js +32 -2
- package/dist/store/sqlite/crm.d.ts +3 -1
- package/dist/store/sqlite/crm.js +37 -20
- package/dist/store/sqlite/deals.js +6 -4
- package/dist/store/sqlite/email-activity.d.ts +4 -0
- package/dist/store/sqlite/email-activity.js +5 -1
- package/dist/store/sqlite/manifest.d.ts +2 -0
- package/dist/store/sqlite/manifest.js +58 -5
- package/dist/store/sqlite/migrations.generated.js +9 -0
- package/dist/store/sqlite/open-store.js +8 -1
- package/dist/store/sqlite/rollback-copy.d.ts +49 -0
- package/dist/store/sqlite/rollback-copy.js +290 -0
- package/dist/store/sqlite/schema.d.ts +32 -0
- package/dist/store/sqlite/schema.js +4 -2
- package/dist/store/store.d.ts +1 -0
- package/dist/store/store.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, statfsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, parse } from "node:path";
|
|
3
|
+
import { CliError } from "@wirecat/cli-base";
|
|
4
|
+
/**
|
|
5
|
+
* A breaking migration (one that raises `min_compatible`) locks older tools out of the store. Before it runs, the
|
|
6
|
+
* base tables it names are copied into a small file in `backups/` beside the store, with the whole schema as it was;
|
|
7
|
+
* `store restore <copy>` puts them back and returns the file to the old version. Derived tables are never copied:
|
|
8
|
+
* search indexes, their queues, vectors, chunks, mentions and involvements are rebuilt from the base tables.
|
|
9
|
+
*/
|
|
10
|
+
const DERIVED = /(_fts|_words|_stems|_trigrams|_pending)$|^(embeddings|chunks|chunk_messages|mentions|involvements|search_terms)$|^vector_index/;
|
|
11
|
+
export const isDerivedTable = (name) => DERIVED.test(name);
|
|
12
|
+
/**
|
|
13
|
+
* Always copied, a few rows each: migrations seed rows into them, and the next upgrade after a rollback would insert
|
|
14
|
+
* those rows again and fail on its own unique keys.
|
|
15
|
+
*/
|
|
16
|
+
const BOOKKEEPING = ["store_settings", "search_index_state"];
|
|
17
|
+
const KEY = "rollback_copy:";
|
|
18
|
+
const SCHEMA = "rollback_copy";
|
|
19
|
+
const ROWID = "__rollback_rowid";
|
|
20
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
// The migration's own write-ahead log needs room too.
|
|
22
|
+
const HEADROOM = 256 * 1024 * 1024;
|
|
23
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
24
|
+
const megabytes = (bytes) => `${Math.ceil(bytes / 1024 / 1024)} MB`;
|
|
25
|
+
const freeBytesOf = (dir) => {
|
|
26
|
+
const { bavail, bsize } = statfsSync(dir);
|
|
27
|
+
return Number(bavail) * Number(bsize);
|
|
28
|
+
};
|
|
29
|
+
const tableIn = (database, schema, name) => database.prepare(`SELECT 1 FROM ${schema}.sqlite_master WHERE type = 'table' AND name = ?`).get(name) !== undefined;
|
|
30
|
+
const keepsRowid = (database, schema, table) => Number(database.prepare("SELECT wr FROM pragma_table_list WHERE schema = ? AND name = ?").get(schema, table)?.wr) ===
|
|
31
|
+
0;
|
|
32
|
+
const columnsOf = (database, schema, table) => database
|
|
33
|
+
.prepare("SELECT name FROM pragma_table_info(?, ?)")
|
|
34
|
+
.all(table, schema)
|
|
35
|
+
.map((row) => String(row.name));
|
|
36
|
+
const versionOf = (database) => Number(database.prepare("SELECT max(version) AS version FROM main.schema_migrations").get()?.version ?? 0);
|
|
37
|
+
const tableBytes = (database, tables) => {
|
|
38
|
+
try {
|
|
39
|
+
return Number(database
|
|
40
|
+
.prepare(`SELECT coalesce(sum(pgsize), 0) AS bytes FROM dbstat
|
|
41
|
+
WHERE schema = 'main' AND aggregate = 1 AND name IN (${tables.map(() => "?").join(", ")})`)
|
|
42
|
+
.get(...tables)?.bytes ?? 0);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Bun's SQLite is built without dbstat: the whole file is the bound.
|
|
46
|
+
const pragma = (name) => Number(database.prepare(`PRAGMA ${name}`).get()?.[name] ?? 0);
|
|
47
|
+
return pragma("page_count") * pragma("page_size");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const stampOf = (at) => new Date(at).toISOString().replace(/[:.]/g, "-");
|
|
51
|
+
const attach = (database, path) => database.prepare(`ATTACH DATABASE ? AS ${SCHEMA}`).run(path);
|
|
52
|
+
/**
|
|
53
|
+
* Outside the migration's transaction (SQLite cannot attach inside one), committed on its own, so a migration that
|
|
54
|
+
* fails leaves it whole. Never blocks the migration: no room, or any failure, is a warning and no copy.
|
|
55
|
+
*/
|
|
56
|
+
export const takeRollbackCopy = (database, { from, to, tables, at }, { store, freeBytes = freeBytesOf, warn = (line) => process.stderr.write(`${line}\n`) }) => {
|
|
57
|
+
const dir = join(dirname(store), "backups");
|
|
58
|
+
const present = [...new Set([...BOOKKEEPING, ...tables])].filter((table) => tableIn(database, "main", table));
|
|
59
|
+
const afterwards = `going back to schema ${from} then needs an empty store, and its data pulled again from the sources`;
|
|
60
|
+
let path;
|
|
61
|
+
try {
|
|
62
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
63
|
+
const needed = tableBytes(database, present);
|
|
64
|
+
const free = freeBytes(dir);
|
|
65
|
+
if (free < needed + HEADROOM) {
|
|
66
|
+
warn(`not enough free disk space for a rollback copy before schema ${to} (about ${megabytes(needed + HEADROOM)} ` +
|
|
67
|
+
`needed, ${megabytes(free)} free in ${dir}) — migrating without one; ${afterwards}`);
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
path = join(dir, `${parse(store).name}-rollback-schema-${from}-${stampOf(at)}.db`);
|
|
71
|
+
writeFileSync(path, "", { flag: "wx", mode: 0o600 });
|
|
72
|
+
attach(database, path);
|
|
73
|
+
try {
|
|
74
|
+
database.exec("BEGIN IMMEDIATE");
|
|
75
|
+
try {
|
|
76
|
+
const copied = versionOf(database);
|
|
77
|
+
database.exec(`CREATE TABLE ${SCHEMA}.rollback_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`);
|
|
78
|
+
database.exec(`CREATE TABLE ${SCHEMA}.rollback_schema AS
|
|
79
|
+
SELECT m.type, m.name, m.tbl_name, m.sql, coalesce(l.type, m.type) AS kind
|
|
80
|
+
FROM main.sqlite_master m LEFT JOIN pragma_table_list l ON l.schema = 'main' AND l.name = m.name
|
|
81
|
+
ORDER BY m.rowid`);
|
|
82
|
+
for (const table of present) {
|
|
83
|
+
const rowid = keepsRowid(database, "main", table) ? `rowid AS ${ROWID}, ` : "";
|
|
84
|
+
database.exec(`CREATE TABLE ${SCHEMA}.${quote(table)} AS SELECT ${rowid}* FROM main.${quote(table)}`);
|
|
85
|
+
}
|
|
86
|
+
const meta = { from: copied, to, tables: present, created_at: at };
|
|
87
|
+
const insert = database.prepare(`INSERT INTO ${SCHEMA}.rollback_meta (key, value) VALUES (?, ?)`);
|
|
88
|
+
for (const [key, value] of Object.entries(meta))
|
|
89
|
+
insert.run(key, JSON.stringify(value));
|
|
90
|
+
database.exec("COMMIT");
|
|
91
|
+
return { path, from: copied, to };
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
database.exec("ROLLBACK");
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
database.exec(`DETACH DATABASE ${SCHEMA}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (path)
|
|
104
|
+
rmSync(path, { force: true });
|
|
105
|
+
warn(`the rollback copy before schema ${to} could not be written (${error instanceof Error ? error.message : String(error)}) — migrating without one; ${afterwards}`);
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/** Inside the migration's transaction: a migration that fails never records its copy, so cleanup never takes it. */
|
|
110
|
+
export const recordRollbackCopy = (database, copy, at) => {
|
|
111
|
+
database
|
|
112
|
+
.prepare("INSERT OR REPLACE INTO store_settings (key, value, at) VALUES (?, ?, ?)")
|
|
113
|
+
.run(`${KEY}${basename(copy.path)}`, JSON.stringify({ path: copy.path, from: copy.from, to: copy.to, at }), at);
|
|
114
|
+
};
|
|
115
|
+
export const discardRollbackCopy = (copy) => rmSync(copy.path, { force: true });
|
|
116
|
+
/**
|
|
117
|
+
* The day after: a copy recorded by a migration that committed, once the store is still at that version. Only the
|
|
118
|
+
* recorded file is deleted, never anything else in `backups/`.
|
|
119
|
+
*/
|
|
120
|
+
export const pruneRollbackCopies = (database, now) => {
|
|
121
|
+
const recorded = database
|
|
122
|
+
.prepare("SELECT key, value FROM store_settings WHERE key LIKE 'rollback\\_copy:%' ESCAPE '\\'")
|
|
123
|
+
.all();
|
|
124
|
+
if (recorded.length === 0)
|
|
125
|
+
return;
|
|
126
|
+
const version = versionOf(database);
|
|
127
|
+
for (const { key, value } of recorded) {
|
|
128
|
+
let copy;
|
|
129
|
+
try {
|
|
130
|
+
copy = JSON.parse(String(value));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (typeof copy.path !== "string" || now - Number(copy.at) < DAY_MS || version < Number(copy.to))
|
|
136
|
+
continue;
|
|
137
|
+
if (!database.prepare("SELECT 1 FROM schema_migrations WHERE version = ?").get(Number(copy.to)))
|
|
138
|
+
continue;
|
|
139
|
+
rmSync(copy.path, { force: true });
|
|
140
|
+
database.prepare("DELETE FROM store_settings WHERE key = ?").run(String(key));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const objectsOf = (database, sql) => database
|
|
144
|
+
.prepare(sql)
|
|
145
|
+
.all()
|
|
146
|
+
.map((row) => ({
|
|
147
|
+
type: String(row.type),
|
|
148
|
+
name: String(row.name),
|
|
149
|
+
sql: row.sql === null ? null : String(row.sql),
|
|
150
|
+
kind: String(row.kind),
|
|
151
|
+
}));
|
|
152
|
+
const currentObjects = (database) => objectsOf(database, `SELECT m.type, m.name, m.sql, coalesce(l.type, m.type) AS kind
|
|
153
|
+
FROM main.sqlite_master m LEFT JOIN pragma_table_list l ON l.schema = 'main' AND l.name = m.name`);
|
|
154
|
+
const internal = ({ name, kind }) => name.startsWith("sqlite_") || kind === "shadow";
|
|
155
|
+
const metaOf = (database) => {
|
|
156
|
+
const values = new Map(database
|
|
157
|
+
.prepare(`SELECT key, value FROM ${SCHEMA}.rollback_meta`)
|
|
158
|
+
.all()
|
|
159
|
+
.map((row) => [String(row.key), JSON.parse(String(row.value))]));
|
|
160
|
+
const tables = values.get("tables");
|
|
161
|
+
return {
|
|
162
|
+
from: Number(values.get("from")),
|
|
163
|
+
to: Number(values.get("to")),
|
|
164
|
+
tables: Array.isArray(tables) ? tables.map(String) : [],
|
|
165
|
+
createdAt: Number(values.get("created_at")),
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
/** FTS5 tables over a table's own rows; the other search indexes hold their own text and wait for `store rebuild`. */
|
|
169
|
+
const contentOf = (sql) => /\bcontent\s*=\s*'([^']+)'/i.exec(sql ?? "")?.[1];
|
|
170
|
+
const RANK = { table: 0, index: 1, view: 2, trigger: 3 };
|
|
171
|
+
/**
|
|
172
|
+
* Returns the file to the schema the copy saved: every object made since is dropped, every table the migration
|
|
173
|
+
* changed is put back with its rows, a table only extended since is rebuilt to its old shape, and the history
|
|
174
|
+
* goes back to the copy's version. One transaction: on any failure the store is as it was.
|
|
175
|
+
*/
|
|
176
|
+
const rewind = (database, saved, named) => {
|
|
177
|
+
const before = new Map(saved.map((object) => [object.name, object]));
|
|
178
|
+
const same = (object) => before.get(object.name)?.type === object.type && before.get(object.name)?.sql === object.sql;
|
|
179
|
+
const touched = new Set(named);
|
|
180
|
+
const rebuilt = [];
|
|
181
|
+
// A trigger or view over a table dropped or rebuilt below would point nowhere; all of them come back from the copy.
|
|
182
|
+
for (const object of currentObjects(database)) {
|
|
183
|
+
if (object.type === "trigger")
|
|
184
|
+
database.exec(`DROP TRIGGER IF EXISTS ${quote(object.name)}`);
|
|
185
|
+
if (object.type === "view")
|
|
186
|
+
database.exec(`DROP VIEW IF EXISTS ${quote(object.name)}`);
|
|
187
|
+
if (object.type === "index" && object.sql !== null && !same(object))
|
|
188
|
+
database.exec(`DROP INDEX IF EXISTS ${quote(object.name)}`);
|
|
189
|
+
}
|
|
190
|
+
for (const object of currentObjects(database)) {
|
|
191
|
+
if (object.type !== "table" || internal(object))
|
|
192
|
+
continue;
|
|
193
|
+
const old = before.get(object.name);
|
|
194
|
+
if (named.has(object.name) || !old || (object.kind === "virtual" && !same(object))) {
|
|
195
|
+
database.exec(`DROP TABLE ${quote(object.name)}`);
|
|
196
|
+
}
|
|
197
|
+
else if (!same(object) && old.sql) {
|
|
198
|
+
rebuild(database, object.name, old.sql);
|
|
199
|
+
rebuilt.push(object.name);
|
|
200
|
+
touched.add(object.name);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const present = new Set(currentObjects(database).map(({ name }) => name));
|
|
204
|
+
const missing = saved
|
|
205
|
+
.filter((object) => !internal(object) && object.sql !== null && !present.has(object.name))
|
|
206
|
+
.sort((a, b) => (RANK[a.type] ?? 9) - (RANK[b.type] ?? 9));
|
|
207
|
+
for (const object of missing) {
|
|
208
|
+
database.exec(object.sql);
|
|
209
|
+
if (object.type === "table" && named.has(object.name))
|
|
210
|
+
restoreRows(database, object.name);
|
|
211
|
+
if (object.kind === "virtual")
|
|
212
|
+
touched.add(object.name);
|
|
213
|
+
}
|
|
214
|
+
for (const object of saved) {
|
|
215
|
+
const content = contentOf(object.sql);
|
|
216
|
+
if (object.kind !== "virtual" || !(content && (touched.has(content) || touched.has(object.name))))
|
|
217
|
+
continue;
|
|
218
|
+
database.exec(`INSERT INTO ${quote(object.name)} (${quote(object.name)}) VALUES ('rebuild')`);
|
|
219
|
+
}
|
|
220
|
+
return rebuilt;
|
|
221
|
+
};
|
|
222
|
+
const rebuild = (database, table, sql) => {
|
|
223
|
+
const aside = `__rollback_${table}`;
|
|
224
|
+
database.exec(`ALTER TABLE ${quote(table)} RENAME TO ${quote(aside)}`);
|
|
225
|
+
database.exec(sql);
|
|
226
|
+
const kept = new Set(columnsOf(database, "main", aside));
|
|
227
|
+
const columns = columnsOf(database, "main", table).filter((column) => kept.has(column));
|
|
228
|
+
const rowid = keepsRowid(database, "main", table) ? ["rowid"] : [];
|
|
229
|
+
const list = [...rowid, ...columns.map(quote)].join(", ");
|
|
230
|
+
database.exec(`INSERT INTO ${quote(table)} (${list}) SELECT ${list} FROM ${quote(aside)}`);
|
|
231
|
+
database.exec(`DROP TABLE ${quote(aside)}`);
|
|
232
|
+
};
|
|
233
|
+
const restoreRows = (database, table) => {
|
|
234
|
+
const copied = new Set(columnsOf(database, SCHEMA, table));
|
|
235
|
+
const columns = columnsOf(database, "main", table).filter((column) => copied.has(column));
|
|
236
|
+
const rowid = keepsRowid(database, "main", table) && copied.has(ROWID);
|
|
237
|
+
const into = [...(rowid ? ["rowid"] : []), ...columns.map(quote)].join(", ");
|
|
238
|
+
const from = [...(rowid ? [ROWID] : []), ...columns.map(quote)].join(", ");
|
|
239
|
+
database.exec(`INSERT INTO main.${quote(table)} (${into}) SELECT ${from} FROM ${SCHEMA}.${quote(table)}`);
|
|
240
|
+
};
|
|
241
|
+
/** Puts a rollback copy back into the store, in place. The caller makes sure nothing else has the store open. */
|
|
242
|
+
export const rollBack = (database, path) => {
|
|
243
|
+
attach(database, path);
|
|
244
|
+
let result;
|
|
245
|
+
try {
|
|
246
|
+
if (!tableIn(database, SCHEMA, "rollback_meta"))
|
|
247
|
+
throw new CliError("validation_error", `${path} is not a rollback copy`);
|
|
248
|
+
const meta = metaOf(database);
|
|
249
|
+
const was = versionOf(database);
|
|
250
|
+
if (!database.prepare("SELECT 1 FROM main.schema_migrations WHERE version = ?").get(meta.to)) {
|
|
251
|
+
throw new CliError("validation_error", `the store is at schema ${was} and was never migrated to ${meta.to}, the schema this copy was taken before — nothing to roll back`);
|
|
252
|
+
}
|
|
253
|
+
const saved = objectsOf(database, `SELECT type, name, sql, kind FROM ${SCHEMA}.rollback_schema ORDER BY rowid`);
|
|
254
|
+
database.exec("PRAGMA foreign_keys = OFF");
|
|
255
|
+
// Renaming a table aside must not rewrite the references other tables hold to it.
|
|
256
|
+
database.exec("PRAGMA legacy_alter_table = ON");
|
|
257
|
+
try {
|
|
258
|
+
database.exec("BEGIN IMMEDIATE");
|
|
259
|
+
try {
|
|
260
|
+
const named = new Set(meta.tables);
|
|
261
|
+
const rebuilt = rewind(database, saved, named);
|
|
262
|
+
database.prepare("DELETE FROM main.schema_migrations WHERE version > ?").run(meta.from);
|
|
263
|
+
database.prepare("DELETE FROM main.store_settings WHERE key = ?").run(`${KEY}${basename(path)}`);
|
|
264
|
+
database.exec("COMMIT");
|
|
265
|
+
const count = (table) => Number(database.prepare(`SELECT count(*) AS n FROM main.${quote(table)}`).get()?.n ?? 0);
|
|
266
|
+
result = {
|
|
267
|
+
...meta,
|
|
268
|
+
was,
|
|
269
|
+
restored: meta.tables.map((table) => ({ table, rows: count(table) })),
|
|
270
|
+
rebuilt,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
database.exec("ROLLBACK");
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
database.exec("PRAGMA legacy_alter_table = OFF");
|
|
280
|
+
database.exec("PRAGMA foreign_keys = ON");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
database.exec(`DETACH DATABASE ${SCHEMA}`);
|
|
285
|
+
}
|
|
286
|
+
// Only the tables put back: a check of the whole store reads every row of it.
|
|
287
|
+
const checked = [...result.restored.map(({ table }) => table), ...result.rebuilt];
|
|
288
|
+
const foreignKeyViolations = checked.reduce((total, table) => total + database.prepare(`PRAGMA foreign_key_check(${quote(table)})`).all().length, 0);
|
|
289
|
+
return { ...result, foreignKeyViolations };
|
|
290
|
+
};
|
|
@@ -15527,6 +15527,22 @@ export declare const pipelines: import("drizzle-orm/sqlite-core").SQLiteTableWit
|
|
|
15527
15527
|
identity: undefined;
|
|
15528
15528
|
generated: undefined;
|
|
15529
15529
|
}, {}>;
|
|
15530
|
+
deletedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
15531
|
+
name: string;
|
|
15532
|
+
tableName: "pipelines";
|
|
15533
|
+
dataType: "number int53";
|
|
15534
|
+
data: number;
|
|
15535
|
+
driverParam: number;
|
|
15536
|
+
notNull: false;
|
|
15537
|
+
hasDefault: false;
|
|
15538
|
+
isPrimaryKey: false;
|
|
15539
|
+
isAutoincrement: false;
|
|
15540
|
+
hasRuntimeDefault: false;
|
|
15541
|
+
enumValues: undefined;
|
|
15542
|
+
baseColumn: never;
|
|
15543
|
+
identity: undefined;
|
|
15544
|
+
generated: undefined;
|
|
15545
|
+
}, {}>;
|
|
15530
15546
|
};
|
|
15531
15547
|
dialect: 'sqlite';
|
|
15532
15548
|
isAlias: false;
|
|
@@ -15679,6 +15695,22 @@ export declare const pipelineStages: import("drizzle-orm/sqlite-core").SQLiteTab
|
|
|
15679
15695
|
identity: undefined;
|
|
15680
15696
|
generated: undefined;
|
|
15681
15697
|
}, {}>;
|
|
15698
|
+
deletedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
15699
|
+
name: string;
|
|
15700
|
+
tableName: "pipeline_stages";
|
|
15701
|
+
dataType: "number int53";
|
|
15702
|
+
data: number;
|
|
15703
|
+
driverParam: number;
|
|
15704
|
+
notNull: false;
|
|
15705
|
+
hasDefault: false;
|
|
15706
|
+
isPrimaryKey: false;
|
|
15707
|
+
isAutoincrement: false;
|
|
15708
|
+
hasRuntimeDefault: false;
|
|
15709
|
+
enumValues: undefined;
|
|
15710
|
+
baseColumn: never;
|
|
15711
|
+
identity: undefined;
|
|
15712
|
+
generated: undefined;
|
|
15713
|
+
}, {}>;
|
|
15682
15714
|
};
|
|
15683
15715
|
dialect: 'sqlite';
|
|
15684
15716
|
isAlias: false;
|
|
@@ -1545,9 +1545,10 @@ export const pipelines = sqliteTable("pipelines", {
|
|
|
1545
1545
|
nameFolded: text("name_folded").notNull(),
|
|
1546
1546
|
createdAt: integer("created_at").notNull(),
|
|
1547
1547
|
updatedAt: integer("updated_at").notNull(),
|
|
1548
|
+
deletedAt: integer("deleted_at"),
|
|
1548
1549
|
}, (table) => [
|
|
1549
|
-
uniqueIndex("
|
|
1550
|
-
.on(table.accountId, table.externalId)
|
|
1550
|
+
uniqueIndex("pipelines_by_account_id_kind_external_id")
|
|
1551
|
+
.on(table.accountId, table.kind, table.externalId)
|
|
1551
1552
|
.where(sql `account_id IS NOT NULL AND external_id IS NOT NULL`),
|
|
1552
1553
|
uniqueIndex("pipelines_by_name_folded_kind").on(table.nameFolded, table.kind).where(sql `account_id IS NULL`),
|
|
1553
1554
|
]);
|
|
@@ -1563,6 +1564,7 @@ export const pipelineStages = sqliteTable("pipeline_stages", {
|
|
|
1563
1564
|
externalId: text("external_id"),
|
|
1564
1565
|
createdAt: integer("created_at").notNull(),
|
|
1565
1566
|
updatedAt: integer("updated_at").notNull(),
|
|
1567
|
+
deletedAt: integer("deleted_at"),
|
|
1566
1568
|
}, (table) => [
|
|
1567
1569
|
unique().on(table.pipelineId, table.nameFolded),
|
|
1568
1570
|
uniqueIndex("pipeline_stages_by_pipeline_id_external_id")
|
package/dist/store/store.d.ts
CHANGED
|
@@ -133,6 +133,7 @@ export interface MessageStore {
|
|
|
133
133
|
export type { ChatHistoryShape, StoredHistorySkip } from "./sqlite/history-skips.js";
|
|
134
134
|
export { BACKFILL_ON_OPEN, type StoreOptions } from "./sqlite/open-store.js";
|
|
135
135
|
export type { ChatScope, NewestMessage, ReplyDelay, WaitingChat } from "./sqlite/reply-times.js";
|
|
136
|
+
export { rollBack } from "./sqlite/rollback-copy.js";
|
|
136
137
|
export { fileFacts, readStoreTables, type TableKind } from "./sqlite/store-tables.js";
|
|
137
138
|
export declare const openStore: (options?: StoreOptions) => Promise<MessageStore>;
|
|
138
139
|
export * from "./exports.js";
|
package/dist/store/store.js
CHANGED
|
@@ -50,6 +50,7 @@ import { syncStoreOver } from "./sync.js";
|
|
|
50
50
|
import { tagsStoreOver } from "./tags.js";
|
|
51
51
|
import { accountVectorsOver, vectorsStoreOver } from "./vectors.js";
|
|
52
52
|
export { BACKFILL_ON_OPEN } from "./sqlite/open-store.js";
|
|
53
|
+
export { rollBack } from "./sqlite/rollback-copy.js";
|
|
53
54
|
export { fileFacts, readStoreTables } from "./sqlite/store-tables.js";
|
|
54
55
|
export const openStore = async (options = {}) => {
|
|
55
56
|
const context = await openStoreContext(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.595.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
"@types/node": "^22.20.5",
|
|
271
271
|
"@types/pngjs": "6.0.5",
|
|
272
272
|
"@vitest/coverage-v8": "^5.0.5",
|
|
273
|
-
"@wirecat/cli-base": "0.80.
|
|
273
|
+
"@wirecat/cli-base": "0.80.2",
|
|
274
274
|
"@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
|
|
275
275
|
"commander": "15.0.0",
|
|
276
276
|
"cspell": "10.4.0",
|