@metamask-previews/wallet-cli 0.0.0-preview-5588db68c → 0.0.0-preview-138a209ad

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
@@ -9,9 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
- - Add SQLite-backed persistence for wallet controller state ([#9067](https://github.com/MetaMask/core/pull/9067))
13
- - A `KeyValueStore` backed by `better-sqlite3` for synchronous reads and writes.
14
- - `loadState` to rehydrate persist-flagged controller state from the store and `subscribeToChanges` to write persist-flagged controller state through to disk on every `stateChanged` event.
15
12
  - Initial package scaffold for `@metamask/wallet-cli`, an [oclif](https://oclif.io)-based `mm` CLI for `@metamask/wallet` ([#9065](https://github.com/MetaMask/core/pull/9065)).
16
13
 
17
14
  [Unreleased]: https://github.com/MetaMask/core/
package/README.md CHANGED
@@ -10,24 +10,6 @@ or
10
10
 
11
11
  `npm install @metamask/wallet-cli`
12
12
 
13
- ## Troubleshooting
14
-
15
- ### Rebuilding `better-sqlite3`
16
-
17
- This package depends on `better-sqlite3`, which ships a native C addon. The monorepo runs Yarn with `enableScripts: false`, so the addon is **not** fetched automatically during `yarn install`. Instead, the package's `test:prepare` script (`scripts/install-binaries.sh`) downloads the matching prebuild on demand the first time you run tests, falling back to compiling the addon from source (via `node-gyp`) when no prebuild is published for your Node ABI/platform.
18
-
19
- If you switch Node versions or branches and the binding is missing, re-run:
20
-
21
- ```sh
22
- yarn workspace @metamask/wallet-cli run test:prepare
23
- ```
24
-
25
- Or invoke `prebuild-install` directly from the monorepo root (where `better-sqlite3` is hoisted):
26
-
27
- ```sh
28
- cd node_modules/better-sqlite3 && node ../.bin/prebuild-install
29
- ```
30
-
31
13
  ## Contributing
32
14
 
33
15
  This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/wallet-cli",
3
- "version": "0.0.0-preview-5588db68c",
3
+ "version": "0.0.0-preview-138a209ad",
4
4
  "description": "The CLI of @metamask/wallet",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -36,24 +36,17 @@
36
36
  "changelog:update": "../../scripts/update-changelog.sh @metamask/wallet-cli",
37
37
  "changelog:validate": "../../scripts/validate-changelog.sh @metamask/wallet-cli",
38
38
  "since-latest-release": "../../scripts/since-latest-release.sh",
39
- "test:prepare": "./scripts/install-binaries.sh",
40
- "test": "yarn test:prepare && NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
39
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
41
40
  "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
42
41
  "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
43
42
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
44
43
  },
45
44
  "dependencies": {
46
- "@metamask/base-controller": "^9.1.0",
47
- "@metamask/utils": "^11.11.0",
48
- "@metamask/wallet": "^3.0.0",
49
- "@oclif/core": "^4.10.5",
50
- "better-sqlite3": "^12.9.0",
51
- "immer": "^9.0.6"
45
+ "@oclif/core": "^4.10.5"
52
46
  },
53
47
  "devDependencies": {
54
48
  "@metamask/auto-changelog": "^6.1.0",
55
49
  "@ts-bridge/cli": "^0.6.4",
56
- "@types/better-sqlite3": "^7.6.13",
57
50
  "@types/jest": "^29.5.14",
58
51
  "deepmerge": "^4.2.2",
59
52
  "jest": "^29.7.0",
@@ -67,6 +60,6 @@
67
60
  "topicSeparator": " "
68
61
  },
69
62
  "engines": {
70
- "node": ">=20"
63
+ "node": "^18.18 || >=20"
71
64
  }
72
65
  }
@@ -1,79 +0,0 @@
1
- "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
- };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
- var _KeyValueStore_db, _KeyValueStore_getStmt, _KeyValueStore_setStmt, _KeyValueStore_deleteStmt, _KeyValueStore_getAllStmt;
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.KeyValueStore = void 0;
19
- const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
20
- /**
21
- * A synchronous key-value store backed by better-sqlite3.
22
- *
23
- * Uses a single `kv` table with TEXT key (primary key) and TEXT value
24
- * (JSON-serialized). Intended as the persistence backend for wallet
25
- * controller state.
26
- */
27
- class KeyValueStore {
28
- constructor(databasePath) {
29
- _KeyValueStore_db.set(this, void 0);
30
- _KeyValueStore_getStmt.set(this, void 0);
31
- _KeyValueStore_setStmt.set(this, void 0);
32
- _KeyValueStore_deleteStmt.set(this, void 0);
33
- _KeyValueStore_getAllStmt.set(this, void 0);
34
- __classPrivateFieldSet(this, _KeyValueStore_db, new better_sqlite3_1.default(databasePath), "f");
35
- __classPrivateFieldGet(this, _KeyValueStore_db, "f").pragma('journal_mode = WAL');
36
- __classPrivateFieldGet(this, _KeyValueStore_db, "f").exec('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
37
- __classPrivateFieldSet(this, _KeyValueStore_getStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('SELECT value FROM kv WHERE key = ?'), "f");
38
- __classPrivateFieldSet(this, _KeyValueStore_setStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)'), "f");
39
- __classPrivateFieldSet(this, _KeyValueStore_deleteStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('DELETE FROM kv WHERE key = ?'), "f");
40
- __classPrivateFieldSet(this, _KeyValueStore_getAllStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('SELECT key, value FROM kv'), "f");
41
- }
42
- get(key) {
43
- const row = __classPrivateFieldGet(this, _KeyValueStore_getStmt, "f").get(key);
44
- if (!row) {
45
- return undefined;
46
- }
47
- try {
48
- return JSON.parse(row.value);
49
- }
50
- catch {
51
- throw new Error(`Failed to parse stored value for key '${key}'`);
52
- }
53
- }
54
- set(key, value) {
55
- __classPrivateFieldGet(this, _KeyValueStore_setStmt, "f").run(key, JSON.stringify(value));
56
- }
57
- getAll() {
58
- const rows = __classPrivateFieldGet(this, _KeyValueStore_getAllStmt, "f").all();
59
- const result = {};
60
- for (const row of rows) {
61
- try {
62
- result[row.key] = JSON.parse(row.value);
63
- }
64
- catch {
65
- throw new Error(`Failed to parse stored value for key '${row.key}'`);
66
- }
67
- }
68
- return result;
69
- }
70
- delete(key) {
71
- __classPrivateFieldGet(this, _KeyValueStore_deleteStmt, "f").run(key);
72
- }
73
- close() {
74
- __classPrivateFieldGet(this, _KeyValueStore_db, "f").close();
75
- }
76
- }
77
- exports.KeyValueStore = KeyValueStore;
78
- _KeyValueStore_db = new WeakMap(), _KeyValueStore_getStmt = new WeakMap(), _KeyValueStore_setStmt = new WeakMap(), _KeyValueStore_deleteStmt = new WeakMap(), _KeyValueStore_getAllStmt = new WeakMap();
79
- //# sourceMappingURL=KeyValueStore.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"KeyValueStore.cjs","sourceRoot":"","sources":["../../src/persistence/KeyValueStore.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AACA,oEAAoC;AAEpC;;;;;;GAMG;AACH,MAAa,aAAa;IAWxB,YAAY,YAAoB;QAVvB,oCAAqB;QAErB,yCAAoE;QAEpE,yCAAmD;QAEnD,4CAA8C;QAE9C,4CAAkE;QAGzE,uBAAA,IAAI,qBAAO,IAAI,wBAAM,CAAC,YAAY,CAAC,MAAA,CAAC;QACpC,uBAAA,IAAI,yBAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACtC,uBAAA,IAAI,yBAAI,CAAC,IAAI,CACX,2EAA2E,CAC5E,CAAC;QAEF,uBAAA,IAAI,0BAAY,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAAC,oCAAoC,CAAC,MAAA,CAAC;QACvE,uBAAA,IAAI,0BAAY,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAC9B,sDAAsD,CACvD,MAAA,CAAC;QACF,uBAAA,IAAI,6BAAe,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,MAAA,CAAC;QACpE,uBAAA,IAAI,6BAAe,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,MAAA,CAAC;IACnE,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,GAAG,GAAG,uBAAA,IAAI,8BAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,GAAG,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAW;QAC1B,uBAAA,IAAI,8BAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,uBAAA,IAAI,iCAAY,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,MAAM,GAAyB,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,uBAAA,IAAI,iCAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK;QACH,uBAAA,IAAI,yBAAI,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF;AA9DD,sCA8DC","sourcesContent":["import type { Json } from '@metamask/utils';\nimport Sqlite from 'better-sqlite3';\n\n/**\n * A synchronous key-value store backed by better-sqlite3.\n *\n * Uses a single `kv` table with TEXT key (primary key) and TEXT value\n * (JSON-serialized). Intended as the persistence backend for wallet\n * controller state.\n */\nexport class KeyValueStore {\n readonly #db: Sqlite.Database;\n\n readonly #getStmt: Sqlite.Statement<[string], { value: string } | undefined>;\n\n readonly #setStmt: Sqlite.Statement<[string, string], void>;\n\n readonly #deleteStmt: Sqlite.Statement<[string], void>;\n\n readonly #getAllStmt: Sqlite.Statement<[], { key: string; value: string }>;\n\n constructor(databasePath: string) {\n this.#db = new Sqlite(databasePath);\n this.#db.pragma('journal_mode = WAL');\n this.#db.exec(\n 'CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)',\n );\n\n this.#getStmt = this.#db.prepare('SELECT value FROM kv WHERE key = ?');\n this.#setStmt = this.#db.prepare(\n 'INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)',\n );\n this.#deleteStmt = this.#db.prepare('DELETE FROM kv WHERE key = ?');\n this.#getAllStmt = this.#db.prepare('SELECT key, value FROM kv');\n }\n\n get(key: string): Json | undefined {\n const row = this.#getStmt.get(key);\n if (!row) {\n return undefined;\n }\n try {\n return JSON.parse(row.value);\n } catch {\n throw new Error(`Failed to parse stored value for key '${key}'`);\n }\n }\n\n set(key: string, value: Json): void {\n this.#setStmt.run(key, JSON.stringify(value));\n }\n\n getAll(): Record<string, Json> {\n const rows = this.#getAllStmt.all();\n const result: Record<string, Json> = {};\n for (const row of rows) {\n try {\n result[row.key] = JSON.parse(row.value);\n } catch {\n throw new Error(`Failed to parse stored value for key '${row.key}'`);\n }\n }\n return result;\n }\n\n delete(key: string): void {\n this.#deleteStmt.run(key);\n }\n\n close(): void {\n this.#db.close();\n }\n}\n"]}
@@ -1,18 +0,0 @@
1
- import type { Json } from "@metamask/utils";
2
- /**
3
- * A synchronous key-value store backed by better-sqlite3.
4
- *
5
- * Uses a single `kv` table with TEXT key (primary key) and TEXT value
6
- * (JSON-serialized). Intended as the persistence backend for wallet
7
- * controller state.
8
- */
9
- export declare class KeyValueStore {
10
- #private;
11
- constructor(databasePath: string);
12
- get(key: string): Json | undefined;
13
- set(key: string, value: Json): void;
14
- getAll(): Record<string, Json>;
15
- delete(key: string): void;
16
- close(): void;
17
- }
18
- //# sourceMappingURL=KeyValueStore.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"KeyValueStore.d.cts","sourceRoot":"","sources":["../../src/persistence/KeyValueStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAG5C;;;;;;GAMG;AACH,qBAAa,aAAa;;gBAWZ,YAAY,EAAE,MAAM;IAehC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAYlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI;IAInC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;IAa9B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,KAAK,IAAI,IAAI;CAGd"}
@@ -1,18 +0,0 @@
1
- import type { Json } from "@metamask/utils";
2
- /**
3
- * A synchronous key-value store backed by better-sqlite3.
4
- *
5
- * Uses a single `kv` table with TEXT key (primary key) and TEXT value
6
- * (JSON-serialized). Intended as the persistence backend for wallet
7
- * controller state.
8
- */
9
- export declare class KeyValueStore {
10
- #private;
11
- constructor(databasePath: string);
12
- get(key: string): Json | undefined;
13
- set(key: string, value: Json): void;
14
- getAll(): Record<string, Json>;
15
- delete(key: string): void;
16
- close(): void;
17
- }
18
- //# sourceMappingURL=KeyValueStore.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"KeyValueStore.d.mts","sourceRoot":"","sources":["../../src/persistence/KeyValueStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAG5C;;;;;;GAMG;AACH,qBAAa,aAAa;;gBAWZ,YAAY,EAAE,MAAM;IAehC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAYlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI;IAInC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;IAa9B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,KAAK,IAAI,IAAI;CAGd"}
@@ -1,79 +0,0 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _KeyValueStore_db, _KeyValueStore_getStmt, _KeyValueStore_setStmt, _KeyValueStore_deleteStmt, _KeyValueStore_getAllStmt;
13
- function $importDefault(module) {
14
- if (module?.__esModule) {
15
- return module.default;
16
- }
17
- return module;
18
- }
19
- import $Sqlite from "better-sqlite3";
20
- const Sqlite = $importDefault($Sqlite);
21
- /**
22
- * A synchronous key-value store backed by better-sqlite3.
23
- *
24
- * Uses a single `kv` table with TEXT key (primary key) and TEXT value
25
- * (JSON-serialized). Intended as the persistence backend for wallet
26
- * controller state.
27
- */
28
- export class KeyValueStore {
29
- constructor(databasePath) {
30
- _KeyValueStore_db.set(this, void 0);
31
- _KeyValueStore_getStmt.set(this, void 0);
32
- _KeyValueStore_setStmt.set(this, void 0);
33
- _KeyValueStore_deleteStmt.set(this, void 0);
34
- _KeyValueStore_getAllStmt.set(this, void 0);
35
- __classPrivateFieldSet(this, _KeyValueStore_db, new Sqlite(databasePath), "f");
36
- __classPrivateFieldGet(this, _KeyValueStore_db, "f").pragma('journal_mode = WAL');
37
- __classPrivateFieldGet(this, _KeyValueStore_db, "f").exec('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
38
- __classPrivateFieldSet(this, _KeyValueStore_getStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('SELECT value FROM kv WHERE key = ?'), "f");
39
- __classPrivateFieldSet(this, _KeyValueStore_setStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)'), "f");
40
- __classPrivateFieldSet(this, _KeyValueStore_deleteStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('DELETE FROM kv WHERE key = ?'), "f");
41
- __classPrivateFieldSet(this, _KeyValueStore_getAllStmt, __classPrivateFieldGet(this, _KeyValueStore_db, "f").prepare('SELECT key, value FROM kv'), "f");
42
- }
43
- get(key) {
44
- const row = __classPrivateFieldGet(this, _KeyValueStore_getStmt, "f").get(key);
45
- if (!row) {
46
- return undefined;
47
- }
48
- try {
49
- return JSON.parse(row.value);
50
- }
51
- catch {
52
- throw new Error(`Failed to parse stored value for key '${key}'`);
53
- }
54
- }
55
- set(key, value) {
56
- __classPrivateFieldGet(this, _KeyValueStore_setStmt, "f").run(key, JSON.stringify(value));
57
- }
58
- getAll() {
59
- const rows = __classPrivateFieldGet(this, _KeyValueStore_getAllStmt, "f").all();
60
- const result = {};
61
- for (const row of rows) {
62
- try {
63
- result[row.key] = JSON.parse(row.value);
64
- }
65
- catch {
66
- throw new Error(`Failed to parse stored value for key '${row.key}'`);
67
- }
68
- }
69
- return result;
70
- }
71
- delete(key) {
72
- __classPrivateFieldGet(this, _KeyValueStore_deleteStmt, "f").run(key);
73
- }
74
- close() {
75
- __classPrivateFieldGet(this, _KeyValueStore_db, "f").close();
76
- }
77
- }
78
- _KeyValueStore_db = new WeakMap(), _KeyValueStore_getStmt = new WeakMap(), _KeyValueStore_setStmt = new WeakMap(), _KeyValueStore_deleteStmt = new WeakMap(), _KeyValueStore_getAllStmt = new WeakMap();
79
- //# sourceMappingURL=KeyValueStore.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"KeyValueStore.mjs","sourceRoot":"","sources":["../../src/persistence/KeyValueStore.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AACA,OAAO,OAAM,uBAAuB;;AAEpC;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IAWxB,YAAY,YAAoB;QAVvB,oCAAqB;QAErB,yCAAoE;QAEpE,yCAAmD;QAEnD,4CAA8C;QAE9C,4CAAkE;QAGzE,uBAAA,IAAI,qBAAO,IAAI,MAAM,CAAC,YAAY,CAAC,MAAA,CAAC;QACpC,uBAAA,IAAI,yBAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACtC,uBAAA,IAAI,yBAAI,CAAC,IAAI,CACX,2EAA2E,CAC5E,CAAC;QAEF,uBAAA,IAAI,0BAAY,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAAC,oCAAoC,CAAC,MAAA,CAAC;QACvE,uBAAA,IAAI,0BAAY,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAC9B,sDAAsD,CACvD,MAAA,CAAC;QACF,uBAAA,IAAI,6BAAe,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,MAAA,CAAC;QACpE,uBAAA,IAAI,6BAAe,uBAAA,IAAI,yBAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,MAAA,CAAC;IACnE,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,GAAG,GAAG,uBAAA,IAAI,8BAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,GAAG,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAW;QAC1B,uBAAA,IAAI,8BAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,uBAAA,IAAI,iCAAY,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,MAAM,GAAyB,EAAE,CAAC;QACxC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,uBAAA,IAAI,iCAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK;QACH,uBAAA,IAAI,yBAAI,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF","sourcesContent":["import type { Json } from '@metamask/utils';\nimport Sqlite from 'better-sqlite3';\n\n/**\n * A synchronous key-value store backed by better-sqlite3.\n *\n * Uses a single `kv` table with TEXT key (primary key) and TEXT value\n * (JSON-serialized). Intended as the persistence backend for wallet\n * controller state.\n */\nexport class KeyValueStore {\n readonly #db: Sqlite.Database;\n\n readonly #getStmt: Sqlite.Statement<[string], { value: string } | undefined>;\n\n readonly #setStmt: Sqlite.Statement<[string, string], void>;\n\n readonly #deleteStmt: Sqlite.Statement<[string], void>;\n\n readonly #getAllStmt: Sqlite.Statement<[], { key: string; value: string }>;\n\n constructor(databasePath: string) {\n this.#db = new Sqlite(databasePath);\n this.#db.pragma('journal_mode = WAL');\n this.#db.exec(\n 'CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)',\n );\n\n this.#getStmt = this.#db.prepare('SELECT value FROM kv WHERE key = ?');\n this.#setStmt = this.#db.prepare(\n 'INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)',\n );\n this.#deleteStmt = this.#db.prepare('DELETE FROM kv WHERE key = ?');\n this.#getAllStmt = this.#db.prepare('SELECT key, value FROM kv');\n }\n\n get(key: string): Json | undefined {\n const row = this.#getStmt.get(key);\n if (!row) {\n return undefined;\n }\n try {\n return JSON.parse(row.value);\n } catch {\n throw new Error(`Failed to parse stored value for key '${key}'`);\n }\n }\n\n set(key: string, value: Json): void {\n this.#setStmt.run(key, JSON.stringify(value));\n }\n\n getAll(): Record<string, Json> {\n const rows = this.#getAllStmt.all();\n const result: Record<string, Json> = {};\n for (const row of rows) {\n try {\n result[row.key] = JSON.parse(row.value);\n } catch {\n throw new Error(`Failed to parse stored value for key '${row.key}'`);\n }\n }\n return result;\n }\n\n delete(key: string): void {\n this.#deleteStmt.run(key);\n }\n\n close(): void {\n this.#db.close();\n }\n}\n"]}
@@ -1,204 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.subscribeToChanges = exports.loadState = void 0;
4
- const utils_1 = require("@metamask/utils");
5
- /**
6
- * Construct a store key from a controller name and property name.
7
- *
8
- * @param controllerName - The controller name.
9
- * @param propertyName - The property name.
10
- * @returns The store key in the format `ControllerName.propertyName`.
11
- */
12
- function storeKey(controllerName, propertyName) {
13
- return `${controllerName}.${propertyName}`;
14
- }
15
- /**
16
- * Load persisted state from the key-value store and reconstruct it as
17
- * a record keyed by controller name.
18
- *
19
- * Keys in the store follow the format `ControllerName.propertyName`.
20
- * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.
21
- *
22
- * Only properties that are currently persist-flagged in `controllerMetadata`
23
- * are rehydrated. Rows for controllers or properties that no longer exist — or
24
- * whose `persist` flag has since been disabled — are ignored. This keeps
25
- * loading symmetric with {@link subscribeToChanges}, which only ever writes
26
- * persist-flagged properties: without the filter, a migration that stops
27
- * persisting a property would leave its stale row on disk to be resurrected
28
- * into the `Wallet` constructor state on the next restart.
29
- *
30
- * @param store - The key-value store to read from.
31
- * @param controllerMetadata - A map from controller name to its state metadata,
32
- * used to filter out keys that are no longer persist-flagged.
33
- * @returns A record of controller states, keyed by controller name, suitable
34
- * for the `state` option of the `Wallet` constructor.
35
- */
36
- function loadState(store, controllerMetadata) {
37
- const allPairs = store.getAll();
38
- const state = {};
39
- for (const [key, value] of Object.entries(allPairs)) {
40
- const dotIndex = key.indexOf('.');
41
- if (dotIndex === -1) {
42
- throw new Error(`Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`);
43
- }
44
- const controllerName = key.slice(0, dotIndex);
45
- const propertyName = key.slice(dotIndex + 1);
46
- if (!controllerName || !propertyName) {
47
- throw new Error(`Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`);
48
- }
49
- if (!isPersisted(controllerMetadata[controllerName], propertyName)) {
50
- continue;
51
- }
52
- if (!state[controllerName]) {
53
- state[controllerName] = {};
54
- }
55
- state[controllerName][propertyName] = value;
56
- }
57
- return state;
58
- }
59
- exports.loadState = loadState;
60
- /**
61
- * Subscribe to all controller `stateChanged` events and persist changes
62
- * to the key-value store.
63
- *
64
- * For each controller's metadata, this function determines which state
65
- * properties are persist-flagged. When a `stateChanged` event fires, it uses
66
- * the Immer patches to identify which top-level properties changed, filters
67
- * to only persist-flagged properties, and writes them to the store.
68
- *
69
- * @param messenger - The root messenger to subscribe on.
70
- * @param controllerMetadata - A map from controller name to its state metadata.
71
- * @param store - The key-value store to write to.
72
- * @param log - Optional logger for persistence-write failures. Defaults to
73
- * `console.error` when omitted. A daemon host should supply its own logger,
74
- * since a backgrounded daemon's stdio may be discarded.
75
- * @returns A function that unsubscribes all persistence handlers.
76
- */
77
- function subscribeToChanges(messenger, controllerMetadata, store, log) {
78
- const unsubscribers = [];
79
- const logFn = log ??
80
- ((message) => {
81
- console.error(message);
82
- });
83
- for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {
84
- const persistedProperties = getPersistPropertyNames(metadata);
85
- if (persistedProperties.size === 0) {
86
- continue;
87
- }
88
- const eventType = `${controllerName}:stateChanged`;
89
- const handler = (state, patches) => {
90
- const changed = getChangedProperties(patches, persistedProperties);
91
- for (const prop of changed) {
92
- const key = storeKey(controllerName, prop);
93
- const removed = !(0, utils_1.hasProperty)(state, prop);
94
- // Derive the value before the try/catch so a throwing `StateDeriver`
95
- // surfaces as its own error instead of a misreported write failure.
96
- const persistFlag = metadata[prop]?.persist;
97
- const value = !removed && typeof persistFlag === 'function'
98
- ? persistFlag(state[prop])
99
- : state[prop];
100
- try {
101
- if (removed) {
102
- store.delete(key);
103
- }
104
- else {
105
- store.set(key, value);
106
- }
107
- }
108
- catch (error) {
109
- // TODO: Surface persistence-write failures up the stack so callers
110
- // can decide to halt rather than continue with diverging in-memory
111
- // and on-disk state. For now, log and continue.
112
- logFn(`Failed to persist state for ${key}: ${String(error)}`);
113
- }
114
- }
115
- };
116
- unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));
117
- }
118
- const unsubscribeAll = () => {
119
- while (unsubscribers.length > 0) {
120
- unsubscribers.pop()?.();
121
- }
122
- };
123
- return unsubscribeAll;
124
- }
125
- exports.subscribeToChanges = subscribeToChanges;
126
- /**
127
- * Subscribe a handler to a controller's `stateChanged` event.
128
- *
129
- * The event name is built from a runtime controller name, so it widens to
130
- * `string` and cannot be proven to be a literal member of the messenger's event
131
- * union at compile time. This helper localizes that single unavoidable cast
132
- * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload
133
- * shape stays compile-checked at every call site instead of being erased by a
134
- * statement-level `@ts-expect-error`.
135
- *
136
- * @param messenger - The root messenger to subscribe on.
137
- * @param eventType - The `${controllerName}:stateChanged` event name.
138
- * @param handler - The state-change handler to register.
139
- * @returns A function that unsubscribes the handler.
140
- */
141
- function subscribeToStateChanged(messenger, eventType, handler) {
142
- const subscriber = messenger;
143
- subscriber.subscribe(eventType, handler);
144
- return () => {
145
- subscriber.unsubscribe(eventType, handler);
146
- };
147
- }
148
- /**
149
- * Determine whether a property is currently persist-flagged.
150
- *
151
- * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,
152
- * and falsy when it is `false` or when the controller or property is absent
153
- * from the metadata. `loadState` and `subscribeToChanges` share this predicate
154
- * so the read and write paths can never disagree on what counts as persisted.
155
- *
156
- * @param metadata - The controller's state metadata, or `undefined` when the
157
- * controller is absent from the metadata map.
158
- * @param propertyName - The property name to check.
159
- * @returns `true` if the property should be persisted.
160
- */
161
- function isPersisted(metadata, propertyName) {
162
- return Boolean(metadata?.[propertyName]?.persist);
163
- }
164
- /**
165
- * Get the set of property names whose `persist` metadata is truthy
166
- * (either `true` or a `StateDeriver` function).
167
- *
168
- * @param metadata - The controller's state metadata.
169
- * @returns A set of property names that should be persisted.
170
- */
171
- function getPersistPropertyNames(metadata) {
172
- const names = new Set();
173
- for (const key of Object.keys(metadata)) {
174
- if (isPersisted(metadata, key)) {
175
- names.add(key);
176
- }
177
- }
178
- return names;
179
- }
180
- /**
181
- * Extracts the set of persist-flagged top-level property names that changed
182
- * from an array of Immer patches.
183
- *
184
- * If any patch has an empty path (indicating a root state replacement),
185
- * all persist-flagged properties are returned.
186
- *
187
- * @param patches - Immer patches from a state update.
188
- * @param persistedProperties - The set of persist-flagged property names.
189
- * @returns A set of top-level property names that were modified.
190
- */
191
- function getChangedProperties(patches, persistedProperties) {
192
- const changed = new Set();
193
- for (const patch of patches) {
194
- if (patch.path.length === 0) {
195
- return persistedProperties;
196
- }
197
- const prop = String(patch.path[0]);
198
- if (persistedProperties.has(prop)) {
199
- changed.add(prop);
200
- }
201
- }
202
- return changed;
203
- }
204
- //# sourceMappingURL=persistence.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persistence.cjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
@@ -1,45 +0,0 @@
1
- import type { StateMetadataConstraint } from "@metamask/base-controller";
2
- import type { Json } from "@metamask/utils";
3
- import type { DefaultActions, DefaultEvents, RootMessenger } from "@metamask/wallet";
4
- import type { KeyValueStore } from "./KeyValueStore.cjs";
5
- /**
6
- * Load persisted state from the key-value store and reconstruct it as
7
- * a record keyed by controller name.
8
- *
9
- * Keys in the store follow the format `ControllerName.propertyName`.
10
- * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.
11
- *
12
- * Only properties that are currently persist-flagged in `controllerMetadata`
13
- * are rehydrated. Rows for controllers or properties that no longer exist — or
14
- * whose `persist` flag has since been disabled — are ignored. This keeps
15
- * loading symmetric with {@link subscribeToChanges}, which only ever writes
16
- * persist-flagged properties: without the filter, a migration that stops
17
- * persisting a property would leave its stale row on disk to be resurrected
18
- * into the `Wallet` constructor state on the next restart.
19
- *
20
- * @param store - The key-value store to read from.
21
- * @param controllerMetadata - A map from controller name to its state metadata,
22
- * used to filter out keys that are no longer persist-flagged.
23
- * @returns A record of controller states, keyed by controller name, suitable
24
- * for the `state` option of the `Wallet` constructor.
25
- */
26
- export declare function loadState(store: KeyValueStore, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>): Record<string, Record<string, Json>>;
27
- /**
28
- * Subscribe to all controller `stateChanged` events and persist changes
29
- * to the key-value store.
30
- *
31
- * For each controller's metadata, this function determines which state
32
- * properties are persist-flagged. When a `stateChanged` event fires, it uses
33
- * the Immer patches to identify which top-level properties changed, filters
34
- * to only persist-flagged properties, and writes them to the store.
35
- *
36
- * @param messenger - The root messenger to subscribe on.
37
- * @param controllerMetadata - A map from controller name to its state metadata.
38
- * @param store - The key-value store to write to.
39
- * @param log - Optional logger for persistence-write failures. Defaults to
40
- * `console.error` when omitted. A daemon host should supply its own logger,
41
- * since a backgrounded daemon's stdio may be discarded.
42
- * @returns A function that unsubscribes all persistence handlers.
43
- */
44
- export declare function subscribeToChanges(messenger: RootMessenger<DefaultActions, DefaultEvents>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
45
- //# sourceMappingURL=persistence.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persistence.d.cts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EACvD,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
@@ -1,45 +0,0 @@
1
- import type { StateMetadataConstraint } from "@metamask/base-controller";
2
- import type { Json } from "@metamask/utils";
3
- import type { DefaultActions, DefaultEvents, RootMessenger } from "@metamask/wallet";
4
- import type { KeyValueStore } from "./KeyValueStore.mjs";
5
- /**
6
- * Load persisted state from the key-value store and reconstruct it as
7
- * a record keyed by controller name.
8
- *
9
- * Keys in the store follow the format `ControllerName.propertyName`.
10
- * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.
11
- *
12
- * Only properties that are currently persist-flagged in `controllerMetadata`
13
- * are rehydrated. Rows for controllers or properties that no longer exist — or
14
- * whose `persist` flag has since been disabled — are ignored. This keeps
15
- * loading symmetric with {@link subscribeToChanges}, which only ever writes
16
- * persist-flagged properties: without the filter, a migration that stops
17
- * persisting a property would leave its stale row on disk to be resurrected
18
- * into the `Wallet` constructor state on the next restart.
19
- *
20
- * @param store - The key-value store to read from.
21
- * @param controllerMetadata - A map from controller name to its state metadata,
22
- * used to filter out keys that are no longer persist-flagged.
23
- * @returns A record of controller states, keyed by controller name, suitable
24
- * for the `state` option of the `Wallet` constructor.
25
- */
26
- export declare function loadState(store: KeyValueStore, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>): Record<string, Record<string, Json>>;
27
- /**
28
- * Subscribe to all controller `stateChanged` events and persist changes
29
- * to the key-value store.
30
- *
31
- * For each controller's metadata, this function determines which state
32
- * properties are persist-flagged. When a `stateChanged` event fires, it uses
33
- * the Immer patches to identify which top-level properties changed, filters
34
- * to only persist-flagged properties, and writes them to the store.
35
- *
36
- * @param messenger - The root messenger to subscribe on.
37
- * @param controllerMetadata - A map from controller name to its state metadata.
38
- * @param store - The key-value store to write to.
39
- * @param log - Optional logger for persistence-write failures. Defaults to
40
- * `console.error` when omitted. A daemon host should supply its own logger,
41
- * since a backgrounded daemon's stdio may be discarded.
42
- * @returns A function that unsubscribes all persistence handlers.
43
- */
44
- export declare function subscribeToChanges(messenger: RootMessenger<DefaultActions, DefaultEvents>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
45
- //# sourceMappingURL=persistence.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persistence.d.mts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EACvD,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
@@ -1,199 +0,0 @@
1
- import { hasProperty } from "@metamask/utils";
2
- /**
3
- * Construct a store key from a controller name and property name.
4
- *
5
- * @param controllerName - The controller name.
6
- * @param propertyName - The property name.
7
- * @returns The store key in the format `ControllerName.propertyName`.
8
- */
9
- function storeKey(controllerName, propertyName) {
10
- return `${controllerName}.${propertyName}`;
11
- }
12
- /**
13
- * Load persisted state from the key-value store and reconstruct it as
14
- * a record keyed by controller name.
15
- *
16
- * Keys in the store follow the format `ControllerName.propertyName`.
17
- * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.
18
- *
19
- * Only properties that are currently persist-flagged in `controllerMetadata`
20
- * are rehydrated. Rows for controllers or properties that no longer exist — or
21
- * whose `persist` flag has since been disabled — are ignored. This keeps
22
- * loading symmetric with {@link subscribeToChanges}, which only ever writes
23
- * persist-flagged properties: without the filter, a migration that stops
24
- * persisting a property would leave its stale row on disk to be resurrected
25
- * into the `Wallet` constructor state on the next restart.
26
- *
27
- * @param store - The key-value store to read from.
28
- * @param controllerMetadata - A map from controller name to its state metadata,
29
- * used to filter out keys that are no longer persist-flagged.
30
- * @returns A record of controller states, keyed by controller name, suitable
31
- * for the `state` option of the `Wallet` constructor.
32
- */
33
- export function loadState(store, controllerMetadata) {
34
- const allPairs = store.getAll();
35
- const state = {};
36
- for (const [key, value] of Object.entries(allPairs)) {
37
- const dotIndex = key.indexOf('.');
38
- if (dotIndex === -1) {
39
- throw new Error(`Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`);
40
- }
41
- const controllerName = key.slice(0, dotIndex);
42
- const propertyName = key.slice(dotIndex + 1);
43
- if (!controllerName || !propertyName) {
44
- throw new Error(`Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`);
45
- }
46
- if (!isPersisted(controllerMetadata[controllerName], propertyName)) {
47
- continue;
48
- }
49
- if (!state[controllerName]) {
50
- state[controllerName] = {};
51
- }
52
- state[controllerName][propertyName] = value;
53
- }
54
- return state;
55
- }
56
- /**
57
- * Subscribe to all controller `stateChanged` events and persist changes
58
- * to the key-value store.
59
- *
60
- * For each controller's metadata, this function determines which state
61
- * properties are persist-flagged. When a `stateChanged` event fires, it uses
62
- * the Immer patches to identify which top-level properties changed, filters
63
- * to only persist-flagged properties, and writes them to the store.
64
- *
65
- * @param messenger - The root messenger to subscribe on.
66
- * @param controllerMetadata - A map from controller name to its state metadata.
67
- * @param store - The key-value store to write to.
68
- * @param log - Optional logger for persistence-write failures. Defaults to
69
- * `console.error` when omitted. A daemon host should supply its own logger,
70
- * since a backgrounded daemon's stdio may be discarded.
71
- * @returns A function that unsubscribes all persistence handlers.
72
- */
73
- export function subscribeToChanges(messenger, controllerMetadata, store, log) {
74
- const unsubscribers = [];
75
- const logFn = log ??
76
- ((message) => {
77
- console.error(message);
78
- });
79
- for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {
80
- const persistedProperties = getPersistPropertyNames(metadata);
81
- if (persistedProperties.size === 0) {
82
- continue;
83
- }
84
- const eventType = `${controllerName}:stateChanged`;
85
- const handler = (state, patches) => {
86
- const changed = getChangedProperties(patches, persistedProperties);
87
- for (const prop of changed) {
88
- const key = storeKey(controllerName, prop);
89
- const removed = !hasProperty(state, prop);
90
- // Derive the value before the try/catch so a throwing `StateDeriver`
91
- // surfaces as its own error instead of a misreported write failure.
92
- const persistFlag = metadata[prop]?.persist;
93
- const value = !removed && typeof persistFlag === 'function'
94
- ? persistFlag(state[prop])
95
- : state[prop];
96
- try {
97
- if (removed) {
98
- store.delete(key);
99
- }
100
- else {
101
- store.set(key, value);
102
- }
103
- }
104
- catch (error) {
105
- // TODO: Surface persistence-write failures up the stack so callers
106
- // can decide to halt rather than continue with diverging in-memory
107
- // and on-disk state. For now, log and continue.
108
- logFn(`Failed to persist state for ${key}: ${String(error)}`);
109
- }
110
- }
111
- };
112
- unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));
113
- }
114
- const unsubscribeAll = () => {
115
- while (unsubscribers.length > 0) {
116
- unsubscribers.pop()?.();
117
- }
118
- };
119
- return unsubscribeAll;
120
- }
121
- /**
122
- * Subscribe a handler to a controller's `stateChanged` event.
123
- *
124
- * The event name is built from a runtime controller name, so it widens to
125
- * `string` and cannot be proven to be a literal member of the messenger's event
126
- * union at compile time. This helper localizes that single unavoidable cast
127
- * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload
128
- * shape stays compile-checked at every call site instead of being erased by a
129
- * statement-level `@ts-expect-error`.
130
- *
131
- * @param messenger - The root messenger to subscribe on.
132
- * @param eventType - The `${controllerName}:stateChanged` event name.
133
- * @param handler - The state-change handler to register.
134
- * @returns A function that unsubscribes the handler.
135
- */
136
- function subscribeToStateChanged(messenger, eventType, handler) {
137
- const subscriber = messenger;
138
- subscriber.subscribe(eventType, handler);
139
- return () => {
140
- subscriber.unsubscribe(eventType, handler);
141
- };
142
- }
143
- /**
144
- * Determine whether a property is currently persist-flagged.
145
- *
146
- * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,
147
- * and falsy when it is `false` or when the controller or property is absent
148
- * from the metadata. `loadState` and `subscribeToChanges` share this predicate
149
- * so the read and write paths can never disagree on what counts as persisted.
150
- *
151
- * @param metadata - The controller's state metadata, or `undefined` when the
152
- * controller is absent from the metadata map.
153
- * @param propertyName - The property name to check.
154
- * @returns `true` if the property should be persisted.
155
- */
156
- function isPersisted(metadata, propertyName) {
157
- return Boolean(metadata?.[propertyName]?.persist);
158
- }
159
- /**
160
- * Get the set of property names whose `persist` metadata is truthy
161
- * (either `true` or a `StateDeriver` function).
162
- *
163
- * @param metadata - The controller's state metadata.
164
- * @returns A set of property names that should be persisted.
165
- */
166
- function getPersistPropertyNames(metadata) {
167
- const names = new Set();
168
- for (const key of Object.keys(metadata)) {
169
- if (isPersisted(metadata, key)) {
170
- names.add(key);
171
- }
172
- }
173
- return names;
174
- }
175
- /**
176
- * Extracts the set of persist-flagged top-level property names that changed
177
- * from an array of Immer patches.
178
- *
179
- * If any patch has an empty path (indicating a root state replacement),
180
- * all persist-flagged properties are returned.
181
- *
182
- * @param patches - Immer patches from a state update.
183
- * @param persistedProperties - The set of persist-flagged property names.
184
- * @returns A set of top-level property names that were modified.
185
- */
186
- function getChangedProperties(patches, persistedProperties) {
187
- const changed = new Set();
188
- for (const patch of patches) {
189
- if (patch.path.length === 0) {
190
- return persistedProperties;
191
- }
192
- const prop = String(patch.path[0]);
193
- if (persistedProperties.has(prop)) {
194
- changed.add(prop);
195
- }
196
- }
197
- return changed;
198
- }
199
- //# sourceMappingURL=persistence.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persistence.mjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}