@wirecat/cli-core 0.595.0 → 0.596.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
CHANGED
|
@@ -5,7 +5,20 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 0.596.0 — 06.11.2026
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`package.json` says which store schema the version speaks.** `wirecat.store` holds `speaks`, the newest schema
|
|
13
|
+
it migrates a file to, and `minCompatible`, the oldest schema that can still use a file it migrated, so an
|
|
14
|
+
installer can check a tool against the owner's store from the npm registry before installing it.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **A read-only open migrates an older store first.** `openStore({ readOnly: true })` on a file at an older schema
|
|
19
|
+
opens it the ordinary way once, which migrates it, then opens it read-only, instead of refusing it with
|
|
20
|
+
`configuration_error`. A tool whose first command only reads no longer needs a workaround. A newer incompatible
|
|
21
|
+
schema is still refused, and a missing file is still not created.
|
|
9
22
|
|
|
10
23
|
## 0.595.0 — 06.11.2026
|
|
11
24
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type DecisionPolicy } from "../decisions.js";
|
|
2
2
|
import { type StoreContext } from "./open.js";
|
|
3
3
|
export interface StoreOptions {
|
|
4
|
-
/** Open an existing compatible store without maintenance; SQLite refuses writes. */
|
|
4
|
+
/** Open an existing compatible store without maintenance, migrating an older one first; SQLite refuses writes. */
|
|
5
5
|
readOnly?: boolean;
|
|
6
6
|
path?: string;
|
|
7
7
|
env?: NodeJS.ProcessEnv;
|
|
@@ -16,4 +16,4 @@ export interface StoreOptions {
|
|
|
16
16
|
decisions?: DecisionPolicy;
|
|
17
17
|
}
|
|
18
18
|
export declare const BACKFILL_ON_OPEN = 5000;
|
|
19
|
-
export declare const openStoreContext: (
|
|
19
|
+
export declare const openStoreContext: (options?: StoreOptions) => Promise<StoreContext>;
|
|
@@ -14,7 +14,7 @@ import { logPruner } from "./log-pruning.js";
|
|
|
14
14
|
import { openSqlite } from "./open.js";
|
|
15
15
|
import { movePaymentFacts } from "./payment-facts.js";
|
|
16
16
|
import { loadProviders } from "./providers.js";
|
|
17
|
-
import { assertReadCompatible } from "./read-compatibility.js";
|
|
17
|
+
import { assertReadCompatible, readCompatibility } from "./read-compatibility.js";
|
|
18
18
|
import { pruneRollbackCopies } from "./rollback-copy.js";
|
|
19
19
|
import { stemsState } from "./stems.js";
|
|
20
20
|
import { fillStems, followDefaultStemmers } from "./stems-fill.js";
|
|
@@ -36,7 +36,32 @@ const createWritable = (file) => {
|
|
|
36
36
|
throw new CliError("permission_error", "the message store cannot be written from here; run where it is writable, or set MESSAGING_STORE", { reason: "store.not-writable" });
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
|
|
39
|
+
// A new tool's first command may only read; it migrates the file as its first write would.
|
|
40
|
+
const openReadable = async (file, options) => {
|
|
41
|
+
const first = await openSqlite(file, { readOnly: true });
|
|
42
|
+
try {
|
|
43
|
+
if (readCompatibility(first.database, options.command) === "current")
|
|
44
|
+
return first;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
first.database.close();
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
first.database.close();
|
|
51
|
+
const writable = await openStoreContext({ ...options, path: file, readOnly: false });
|
|
52
|
+
writable.database.close();
|
|
53
|
+
const migrated = await openSqlite(file, { readOnly: true });
|
|
54
|
+
try {
|
|
55
|
+
assertReadCompatible(migrated.database, options.command);
|
|
56
|
+
return migrated;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
migrated.database.close();
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export const openStoreContext = async (options = {}) => {
|
|
64
|
+
const { path, env, now = Date.now, command, version, warn = (line) => process.stderr.write(`${line}\n`), readOnly = false, decisions = DEFAULT_DECISION_POLICY, } = options;
|
|
40
65
|
if (typeof readOnly !== "boolean")
|
|
41
66
|
throw new CliError("validation_error", "readOnly must be a boolean");
|
|
42
67
|
await storeCapable();
|
|
@@ -48,12 +73,10 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
|
|
|
48
73
|
}
|
|
49
74
|
else
|
|
50
75
|
createWritable(file);
|
|
51
|
-
const opened = await openSqlite(file, { readOnly });
|
|
76
|
+
const opened = readOnly ? await openReadable(file, options) : await openSqlite(file, { readOnly });
|
|
52
77
|
const database = opened.database;
|
|
53
78
|
try {
|
|
54
|
-
if (readOnly)
|
|
55
|
-
assertReadCompatible(database, command);
|
|
56
|
-
else {
|
|
79
|
+
if (!readOnly) {
|
|
57
80
|
const notice = migrate(database, {
|
|
58
81
|
now,
|
|
59
82
|
command,
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { CacheDatabase } from "../driver.js";
|
|
2
|
+
/** "older" when the file needs migrating before a read-only open; throws for every file it may not read. */
|
|
3
|
+
export declare const readCompatibility: (database: CacheDatabase, command?: string) => "current" | "older";
|
|
2
4
|
export declare const assertReadCompatible: (database: CacheDatabase, command?: string) => void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { MIGRATIONS } from "../migrations.js";
|
|
3
3
|
import { refusalAdvice, storeClients } from "./clients.js";
|
|
4
|
-
|
|
4
|
+
/** "older" when the file needs migrating before a read-only open; throws for every file it may not read. */
|
|
5
|
+
export const readCompatibility = (database, command) => {
|
|
5
6
|
const ours = MIGRATIONS.at(-1)?.version ?? 0;
|
|
6
7
|
if (!database.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='schema_migrations'").get())
|
|
7
8
|
throw new CliError("configuration_error", "The store has no schema history; initialize it explicitly before a read-only open");
|
|
@@ -15,8 +16,11 @@ export const assertReadCompatible = (database, command) => {
|
|
|
15
16
|
minCompatible < 1 ||
|
|
16
17
|
minCompatible > version)
|
|
17
18
|
throw new CliError("configuration_error", "The store schema history is invalid; read-only opening cannot repair it");
|
|
18
|
-
if (version < ours)
|
|
19
|
-
throw new CliError("configuration_error", `The store needs schema ${ours} maintenance before a read-only open (found ${version})`);
|
|
20
19
|
if (minCompatible > ours)
|
|
21
20
|
throw new CliError("configuration_error", `The store requires schema ${minCompatible}, but this tool supports ${ours}; ${refusalAdvice(storeClients(database), command, version)}`);
|
|
21
|
+
return version < ours ? "older" : "current";
|
|
22
|
+
};
|
|
23
|
+
export const assertReadCompatible = (database, command) => {
|
|
24
|
+
if (readCompatibility(database, command) === "older")
|
|
25
|
+
throw new CliError("configuration_error", `The store needs schema ${MIGRATIONS.at(-1)?.version ?? 0} maintenance before a read-only open`);
|
|
22
26
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.596.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",
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
"url": "https://github.com/WireCatLabs/cli-core/issues"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
|
+
"wirecat": {
|
|
17
|
+
"store": {
|
|
18
|
+
"speaks": 53,
|
|
19
|
+
"minCompatible": 34
|
|
20
|
+
}
|
|
21
|
+
},
|
|
16
22
|
"engines": {
|
|
17
23
|
"node": "^22.16.0 || >=24"
|
|
18
24
|
},
|