@metamask/keyring-sdk 3.1.0 → 4.0.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 +12 -1
- package/dist/migration.cjs +57 -18
- package/dist/migration.cjs.map +1 -1
- package/dist/migration.d.cts +78 -50
- package/dist/migration.d.cts.map +1 -1
- package/dist/migration.d.mts +78 -50
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +57 -18
- package/dist/migration.mjs.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.0.0]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **BREAKING:** Remove envelope from migration framework ([#619](https://github.com/MetaMask/accounts/pull/619))
|
|
15
|
+
- The envelope made it harder to integrate with existing state.
|
|
16
|
+
- We still keep the requirement of having a `version` field to be declared, but the state can be "flatten" instead of living inside the `data` (envelope) field.
|
|
17
|
+
- Bump `@metamask/keyring-api` from `^24.0.0` to `^24.2.0` ([#620](https://github.com/MetaMask/accounts/pull/620), [#660](https://github.com/MetaMask/accounts/pull/660))
|
|
18
|
+
- Bump `@metamask/eth-sig-util` from `^8.2.0` to `^9.0.0` ([#626](https://github.com/MetaMask/accounts/pull/626))
|
|
19
|
+
|
|
10
20
|
## [3.1.0]
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -120,7 +130,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
120
130
|
|
|
121
131
|
- Initial release, extracted from `@metamask/keyring-api` ([#478](https://github.com/MetaMask/accounts/pull/478), [#482](https://github.com/MetaMask/accounts/pull/482))
|
|
122
132
|
|
|
123
|
-
[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-sdk@
|
|
133
|
+
[Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-sdk@4.0.0...HEAD
|
|
134
|
+
[4.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-sdk@3.1.0...@metamask/keyring-sdk@4.0.0
|
|
124
135
|
[3.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-sdk@3.0.0...@metamask/keyring-sdk@3.1.0
|
|
125
136
|
[3.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-sdk@2.3.0...@metamask/keyring-sdk@3.0.0
|
|
126
137
|
[2.3.0]: https://github.com/MetaMask/accounts/compare/@metamask/keyring-sdk@2.2.0...@metamask/keyring-sdk@2.3.0
|
package/dist/migration.cjs
CHANGED
|
@@ -1,35 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createMigrations = exports.isVersionedState = exports.VersionedStateStruct = void 0;
|
|
3
|
+
exports.createMigrations = exports.isVersionedState = exports.VersionedStateStruct = exports.JsonObjectStruct = void 0;
|
|
4
4
|
const superstruct_1 = require("@metamask/superstruct");
|
|
5
5
|
const utils_1 = require("@metamask/utils");
|
|
6
6
|
/**
|
|
7
|
-
* Superstruct schema for
|
|
7
|
+
* Superstruct schema for a plain JSON object (`Record<string, Json>`).
|
|
8
|
+
*
|
|
9
|
+
* Used as the default fallback schema for step input/output validation when no
|
|
10
|
+
* explicit schema is provided.
|
|
11
|
+
*/
|
|
12
|
+
// `record(string(), JsonStruct)` alone would accept arrays: `typeof [] === 'object'`
|
|
13
|
+
// satisfies superstruct's `isObject` check, and `Object.entries(['a', 'b'])` yields
|
|
14
|
+
// valid string keys with JSON values. The refinement makes the array rejection explicit.
|
|
15
|
+
exports.JsonObjectStruct = (0, superstruct_1.refine)((0, superstruct_1.record)((0, superstruct_1.string)(), utils_1.JsonStruct), 'JsonObject', (value) => typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
16
|
+
/**
|
|
17
|
+
* Superstruct schema for an exact `{ version: integer }` object with no other
|
|
18
|
+
* fields.
|
|
19
|
+
*
|
|
20
|
+
* Use this struct when you need to validate a pure version token. To check
|
|
21
|
+
* whether arbitrary flat state carries a version field, use
|
|
22
|
+
* {@link isVersionedState} instead.
|
|
8
23
|
*/
|
|
9
|
-
exports.VersionedStateStruct = (0, superstruct_1.
|
|
24
|
+
exports.VersionedStateStruct = (0, superstruct_1.type)({
|
|
10
25
|
version: (0, superstruct_1.integer)(),
|
|
11
|
-
data: utils_1.JsonStruct,
|
|
12
26
|
});
|
|
13
27
|
/**
|
|
14
|
-
* Type guard to check if a value is a
|
|
28
|
+
* Type guard to check if a value is a plain JSON object.
|
|
29
|
+
*
|
|
30
|
+
* @param value - The value to check.
|
|
31
|
+
* @returns `true` if the value is a non-null, non-array object.
|
|
32
|
+
*/
|
|
33
|
+
function isJsonObject(value) {
|
|
34
|
+
return (0, superstruct_1.is)(value, exports.JsonObjectStruct);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Type guard to check if a value is a {@link VersionedState}.
|
|
38
|
+
*
|
|
39
|
+
* Returns `true` for any plain object that carries a `version: integer` field,
|
|
40
|
+
* regardless of what other fields are present.
|
|
15
41
|
*
|
|
16
42
|
* @param state - The value to check.
|
|
17
|
-
* @returns `true` if the value
|
|
43
|
+
* @returns `true` if the value has a `version: integer` field.
|
|
18
44
|
*/
|
|
19
45
|
function isVersionedState(state) {
|
|
20
46
|
return (0, superstruct_1.is)(state, exports.VersionedStateStruct);
|
|
21
47
|
}
|
|
22
48
|
exports.isVersionedState = isVersionedState;
|
|
23
49
|
/**
|
|
24
|
-
*
|
|
50
|
+
* Extract the version and inner data from state.
|
|
25
51
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
52
|
+
* For versioned state (`isVersionedState` returns true), strips the `version`
|
|
53
|
+
* field and returns the rest as data. For unversioned legacy state (plain
|
|
54
|
+
* object with no `version` field), returns version 0 and the object as-is.
|
|
55
|
+
*
|
|
56
|
+
* @param state - The state to decompose.
|
|
57
|
+
* @returns The version number and the inner data object.
|
|
28
58
|
*/
|
|
29
59
|
function getVersionAndData(state) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
if (!isJsonObject(state)) {
|
|
61
|
+
throw new Error('Unversioned state must be a plain object');
|
|
62
|
+
}
|
|
63
|
+
if (isVersionedState(state)) {
|
|
64
|
+
const { version, ...data } = state;
|
|
65
|
+
return { version, data };
|
|
66
|
+
}
|
|
67
|
+
return { version: 0, data: state };
|
|
33
68
|
}
|
|
34
69
|
/**
|
|
35
70
|
* Apply the pending steps of a chain to `state`.
|
|
@@ -38,7 +73,7 @@ function getVersionAndData(state) {
|
|
|
38
73
|
*
|
|
39
74
|
* @param steps - All steps of the chain.
|
|
40
75
|
* @param state - The serialized keyring state.
|
|
41
|
-
* @returns The migrated state
|
|
76
|
+
* @returns The migrated state in a {@link MigrationResult}.
|
|
42
77
|
*/
|
|
43
78
|
async function applySteps(steps, state) {
|
|
44
79
|
const latestVersion = steps.length;
|
|
@@ -52,13 +87,16 @@ async function applySteps(steps, state) {
|
|
|
52
87
|
const pendingSteps = steps.slice(version);
|
|
53
88
|
let data = initialData;
|
|
54
89
|
for (const step of pendingSteps) {
|
|
55
|
-
(0, superstruct_1.assert)(data, step.inputSchema ??
|
|
90
|
+
(0, superstruct_1.assert)(data, step.inputSchema ?? exports.JsonObjectStruct);
|
|
56
91
|
data = await step.migrate(data);
|
|
57
|
-
(0, superstruct_1.assert)(data, step.outputSchema ??
|
|
92
|
+
(0, superstruct_1.assert)(data, step.outputSchema ?? exports.JsonObjectStruct);
|
|
58
93
|
}
|
|
59
94
|
return {
|
|
60
95
|
version: latestVersion,
|
|
61
|
-
|
|
96
|
+
state: {
|
|
97
|
+
...data,
|
|
98
|
+
version: latestVersion,
|
|
99
|
+
},
|
|
62
100
|
migrated: pendingSteps.length > 0,
|
|
63
101
|
};
|
|
64
102
|
}
|
|
@@ -84,9 +122,10 @@ function buildChain(steps) {
|
|
|
84
122
|
* .add({ migrate: (data) => ({ count: data.numberOfItems }) })
|
|
85
123
|
* .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast
|
|
86
124
|
*
|
|
87
|
-
* const {
|
|
125
|
+
* const { state, migrated } = await migrations.apply(serializedState);
|
|
126
|
+
* // Persist `state`; re-save if `migrated` is true.
|
|
88
127
|
* ```
|
|
89
|
-
* @returns An empty chain
|
|
128
|
+
* @returns An empty chain typed to `JsonObject`.
|
|
90
129
|
*/
|
|
91
130
|
function createMigrations() {
|
|
92
131
|
return buildChain([]);
|
package/dist/migration.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.cjs","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":";;;AACA,uDAAoE;AACpE,2CAA6C;AAG7C;;GAEG;AACU,QAAA,oBAAoB,GAAG,IAAA,oBAAM,EAAC;IACzC,OAAO,EAAE,IAAA,qBAAO,GAAE;IAClB,IAAI,EAAE,kBAAU;CACjB,CAAC,CAAC;AA4BH;;;;;GAKG;AACH,SAAgB,gBAAgB,CAC9B,KAAoC;IAEpC,OAAO,IAAA,gBAAE,EAAC,KAAK,EAAE,4BAAoB,CAAC,CAAC;AACzC,CAAC;AAJD,4CAIC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,KAAoC;IAEpC,OAAO,gBAAgB,CAAC,KAAK,CAAC;QAC5B,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;QAC9C,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAClC,CAAC;AA8ED;;;;;;;;GAQG;AACH,KAAK,UAAU,UAAU,CACvB,KAA+B,EAC/B,KAAW;IAEX,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAEhE,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,oCAAoC,CAC7D,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,+CAA+C,aAAa,EAAE,CACvF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,GAAG,WAAW,CAAC;IAEvB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAA,oBAAM,EAAC,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,kBAAU,CAAC,CAAC;QAC7C,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChC,IAAA,oBAAM,EAAC,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,kBAAU,CAAC,CAAC;IAChD,CAAC;IAED,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,IAAI;QACJ,QAAQ,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC;KACT,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CACjB,KAA+B;IAE/B,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,MAAM;QACrB,GAAG,EAAE,CACH,IAAkC,EAClC,EAAE,CAAC,UAAU,CAAS,CAAC,GAAG,KAAK,EAAE,IAAgC,CAAC,CAAC;QACrE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAO,KAAK,EAAE,KAAK,CAAC;KACvD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,UAAU,CAAO,EAAE,CAAC,CAAC;AAC9B,CAAC;AAFD,4CAEC","sourcesContent":["import type { Infer, Struct } from '@metamask/superstruct';\nimport { assert, integer, is, object } from '@metamask/superstruct';\nimport { JsonStruct } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\n\n/**\n * Superstruct schema for the versioned state envelope.\n */\nexport const VersionedStateStruct = object({\n version: integer(),\n data: JsonStruct,\n});\n\n/**\n * Versioned state envelope wrapping the actual keyring data.\n *\n * After migrations are applied, state is always wrapped in this format. `serialize()`\n * should produce this envelope so that subsequent `deserialize()` calls can detect the\n * version.\n */\nexport type VersionedState<Data extends Json = Json> = Omit<\n Infer<typeof VersionedStateStruct>,\n 'data'\n> & {\n data: Data;\n};\n\n/**\n * Return value of {@link MigrationChain.apply}.\n *\n * Extends {@link VersionedState} with a `migrated` flag that is `true` when at least\n * one step was applied during the call. Callers can use this to detect that the\n * in-memory state has been upgraded and schedule a persist so the new version is\n * written to storage, even when no other state change happens in the session.\n */\nexport type MigrationResult<Data extends Json = Json> = VersionedState<Data> & {\n migrated: boolean;\n};\n\n/**\n * Type guard to check if a value is a {@link VersionedState} envelope.\n *\n * @param state - The value to check.\n * @returns `true` if the value is a versioned state envelope.\n */\nexport function isVersionedState<State extends Json = Json>(\n state: State | VersionedState<State>,\n): state is VersionedState<State> {\n return is(state, VersionedStateStruct);\n}\n\n/**\n * Get the version and data from state, treating unversioned state as version 0.\n *\n * @param state - The state to check.\n * @returns The version and data.\n */\nfunction getVersionAndData<State extends Json = Json>(\n state: State | VersionedState<State>,\n): VersionedState<State> {\n return isVersionedState(state)\n ? { version: state.version, data: state.data }\n : { version: 0, data: state };\n}\n\n/**\n * A single migration step, added to a {@link MigrationChain} via `.add()`.\n *\n * `Input` is bound automatically to the chain's current data type when the step is\n * passed to `.add()`, so `migrate` receives a correctly typed argument with no manual\n * cast.\n */\nexport type MigrationStep<\n Input extends Json = Json,\n Output extends Json = Json,\n> = {\n /**\n * Transform state from the previous step's output to this step's output.\n *\n * Receives the raw inner data (not the versioned envelope). May be sync or async to\n * support complex operations like re-deriving data from secrets.\n *\n * @param data - The state data from the previous step.\n * @returns The migrated data.\n */\n migrate(data: Input): Output | Promise<Output>;\n /**\n * Optional schema validating this step's input before `migrate` is called. Defaults\n * to a generic JSON-shape check when omitted.\n */\n inputSchema?: Struct<Input>;\n /**\n * Optional schema validating this step's output at runtime.\n */\n outputSchema?: Struct<Output>;\n};\n\n/**\n * A chain of migration steps for evolving keyring serialized state across versions.\n *\n * Steps are versioned by position: the first `.add()` call produces version 1, the\n * second version 2, and so on. Create one with {@link createMigrations}.\n */\nexport type MigrationChain<Data extends Json = Json> = {\n /**\n * The number of steps added so far. Since steps are versioned by position starting at\n * 1, this also doubles as \"the latest version\" (use it in `serialize()`).\n */\n readonly version: number;\n /**\n * Append a step to the chain.\n *\n * Returns a new chain typed to `Output`. It does not mutate the chain it's called on,\n * so branching from a shared base chain is safe.\n *\n * `Input` defaults to the chain's current `Data` type. Providing `inputSchema` lets\n * TypeScript infer a narrower `Input` (bounded to extend `Data`), so `migrate`\n * receives a schema-typed argument when narrowing raw state into a specific shape,\n * typically on a chain's first step, where `Data` is `Json`.\n *\n * @param step - The migration step to append.\n * @returns A new chain whose data type is the step's `Output`.\n */\n add<Input extends Data = Data, Output extends Json = Json>(\n step: MigrationStep<Input, Output>,\n ): MigrationChain<Output>;\n /**\n * Apply all pending steps to `state`.\n *\n * Handles both versioned state (wrapped in `{ version, data }` envelope) and\n * unversioned legacy state (treated as version 0).\n *\n * @param state - The serialized keyring state (from vault or previous serialize).\n * @returns The migrated state wrapped in a versioned envelope, plus a `migrated`\n * flag.\n * @throws If `state`'s version is newer than this chain's latest version, or if a\n * step's `inputSchema`/`outputSchema` validation fails.\n */\n apply(state: Json): Promise<MigrationResult<Data>>;\n};\n\n/**\n * Apply the pending steps of a chain to `state`.\n *\n * Implements {@link MigrationChain.apply} for the chain built from `steps`.\n *\n * @param steps - All steps of the chain.\n * @param state - The serialized keyring state.\n * @returns The migrated state wrapped in a versioned envelope, plus a `migrated` flag.\n */\nasync function applySteps<Data extends Json>(\n steps: readonly MigrationStep[],\n state: Json,\n): Promise<MigrationResult<Data>> {\n const latestVersion = steps.length;\n const { version, data: initialData } = getVersionAndData(state);\n\n if (version < 0) {\n throw new Error(\n `State version ${version} is invalid; it cannot be negative`,\n );\n }\n\n if (version > latestVersion) {\n throw new Error(\n `State version ${version} is newer than the latest migration version ${latestVersion}`,\n );\n }\n\n const pendingSteps = steps.slice(version);\n let data = initialData;\n\n for (const step of pendingSteps) {\n assert(data, step.inputSchema ?? JsonStruct);\n data = await step.migrate(data);\n assert(data, step.outputSchema ?? JsonStruct);\n }\n\n return {\n version: latestVersion,\n data,\n migrated: pendingSteps.length > 0,\n } as MigrationResult<Data>;\n}\n\n/**\n * Build a {@link MigrationChain} wrapping the given internal steps.\n *\n * @param steps - The steps accumulated so far.\n * @returns A chain exposing `add`, `version`, and `apply`.\n */\nfunction buildChain<Data extends Json>(\n steps: readonly MigrationStep[],\n): MigrationChain<Data> {\n return {\n version: steps.length,\n add: <Input extends Data, Output extends Json>(\n step: MigrationStep<Input, Output>,\n ) => buildChain<Output>([...steps, step as unknown as MigrationStep]),\n apply: async (state) => applySteps<Data>(steps, state),\n };\n}\n\n/**\n * Start a new, empty migration chain.\n *\n * @example\n * ```typescript\n * const migrations = createMigrations()\n * .add({ migrate: (data) => ({ count: data.numberOfItems }) })\n * .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast\n *\n * const { data } = await migrations.apply(state);\n * ```\n * @returns An empty chain, typed to accept `Json` as its first step's input.\n */\nexport function createMigrations(): MigrationChain<Json> {\n return buildChain<Json>([]);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"migration.cjs","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Infer, Struct } from '@metamask/superstruct';\nimport {\n assert,\n integer,\n is,\n record,\n refine,\n string,\n type,\n} from '@metamask/superstruct';\nimport { JsonStruct } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\n\n/**\n * Constraint for flat migration state: must be a plain JSON object so that the\n * `version` field can be inlined alongside its other fields.\n */\nexport type JsonObject = Record<string, Json>;\n\n/**\n * Superstruct schema for a plain JSON object (`Record<string, Json>`).\n *\n * Used as the default fallback schema for step input/output validation when no\n * explicit schema is provided.\n */\n// `record(string(), JsonStruct)` alone would accept arrays: `typeof [] === 'object'`\n// satisfies superstruct's `isObject` check, and `Object.entries(['a', 'b'])` yields\n// valid string keys with JSON values. The refinement makes the array rejection explicit.\nexport const JsonObjectStruct: Struct<JsonObject> = refine(\n record(string(), JsonStruct),\n 'JsonObject',\n (value) =>\n typeof value === 'object' && value !== null && !Array.isArray(value),\n);\n\n/**\n * Superstruct schema for an exact `{ version: integer }` object with no other\n * fields.\n *\n * Use this struct when you need to validate a pure version token. To check\n * whether arbitrary flat state carries a version field, use\n * {@link isVersionedState} instead.\n */\nexport const VersionedStateStruct = type({\n version: integer(),\n});\n\n/**\n * Flat versioned state: the keyring state with `version` inlined alongside its\n * own fields.\n *\n * This is the format that `serialize()` should produce and `deserialize()` will\n * consume. The `version` field is managed by the migration framework; every\n * other field belongs to the keyring.\n *\n * Do not use `version` or `migrated` as field names in keyring state, as the\n * framework writes both at the top level when constructing a\n * {@link MigrationResult}.\n *\n * @example\n * const state: VersionedState<{ accounts: string[] }> = {\n * version: 3,\n * accounts: [],\n * };\n */\nexport type VersionedState<Data extends JsonObject = JsonObject> = Data &\n Infer<typeof VersionedStateStruct>;\n\n/**\n * Return value of {@link MigrationChain.apply}.\n *\n * `state` is the flat {@link VersionedState} to persist. `migrated` is a\n * runtime-only flag that is never written to storage. `version` duplicates\n * `state.version` for convenience.\n */\nexport type MigrationResult<Data extends JsonObject = JsonObject> = {\n /** Convenience alias for `state.version`. */\n version: number;\n /** The migrated flat state to persist. */\n state: VersionedState<Data>;\n /** `true` when at least one migration step was applied during this call. */\n migrated: boolean;\n};\n\n/**\n * Type guard to check if a value is a plain JSON object.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a non-null, non-array object.\n */\nfunction isJsonObject(value: Json): value is JsonObject {\n return is(value, JsonObjectStruct);\n}\n\n/**\n * Type guard to check if a value is a {@link VersionedState}.\n *\n * Returns `true` for any plain object that carries a `version: integer` field,\n * regardless of what other fields are present.\n *\n * @param state - The value to check.\n * @returns `true` if the value has a `version: integer` field.\n */\nexport function isVersionedState<State extends JsonObject = JsonObject>(\n state: State | VersionedState<State>,\n): state is VersionedState<State> {\n return is(state, VersionedStateStruct);\n}\n\n/**\n * Extract the version and inner data from state.\n *\n * For versioned state (`isVersionedState` returns true), strips the `version`\n * field and returns the rest as data. For unversioned legacy state (plain\n * object with no `version` field), returns version 0 and the object as-is.\n *\n * @param state - The state to decompose.\n * @returns The version number and the inner data object.\n */\nfunction getVersionAndData(state: Json): { version: number; data: JsonObject } {\n if (!isJsonObject(state)) {\n throw new Error('Unversioned state must be a plain object');\n }\n\n if (isVersionedState(state)) {\n const { version, ...data } = state;\n return { version, data };\n }\n\n return { version: 0, data: state };\n}\n\n/**\n * A single migration step, added to a {@link MigrationChain} via `.add()`.\n *\n * `Input` is bound automatically to the chain's current data type when the\n * step is passed to `.add()`, so `migrate` receives a correctly typed argument\n * with no manual cast.\n */\nexport type MigrationStep<\n Input extends JsonObject = JsonObject,\n Output extends JsonObject = JsonObject,\n> = {\n /**\n * Transform state from the previous step's output to this step's output.\n *\n * Receives the raw inner data (not the versioned envelope). May be sync or\n * async to support complex operations like re-deriving data from secrets.\n *\n * @param data - The state data from the previous step.\n * @returns The migrated data.\n */\n migrate(data: Input): Output | Promise<Output>;\n /**\n * Optional schema validating this step's input before `migrate` is called.\n */\n inputSchema?: Struct<Input>;\n /**\n * Optional schema validating this step's output at runtime.\n */\n outputSchema?: Struct<Output>;\n};\n\n/**\n * A chain of migration steps for evolving keyring serialized state across\n * versions.\n *\n * Steps are versioned by position: the first `.add()` call produces version 1,\n * the second version 2, and so on. Create one with {@link createMigrations}.\n */\nexport type MigrationChain<Data extends JsonObject = JsonObject> = {\n /**\n * The number of steps added so far. Also the latest version number (use it\n * in `serialize()`).\n */\n readonly version: number;\n /**\n * Append a step to the chain.\n *\n * Returns a new chain typed to `Output`. Does not mutate the chain it is\n * called on, so branching from a shared base chain is safe.\n *\n * @param step - The migration step to append.\n * @returns A new chain whose data type is the step's `Output`.\n */\n add<Input extends Data = Data, Output extends JsonObject = JsonObject>(\n step: MigrationStep<Input, Output>,\n ): MigrationChain<Output>;\n /**\n * Apply all pending steps to `state`.\n *\n * Handles both flat versioned state (with a `version` field) and unversioned\n * legacy state (plain object without `version`, treated as version 0).\n *\n * @param state - The serialized keyring state (from vault or previous\n * serialize).\n * @returns The migrated state wrapped in a {@link MigrationResult}.\n * @throws If `state` is not a plain object, if its version is newer than the\n * chain's latest version, or if a step's `inputSchema`/`outputSchema`\n * validation fails.\n */\n apply(state: Json): Promise<MigrationResult<Data>>;\n};\n\n/**\n * Apply the pending steps of a chain to `state`.\n *\n * Implements {@link MigrationChain.apply} for the chain built from `steps`.\n *\n * @param steps - All steps of the chain.\n * @param state - The serialized keyring state.\n * @returns The migrated state in a {@link MigrationResult}.\n */\nasync function applySteps<Data extends JsonObject>(\n steps: readonly MigrationStep[],\n state: Json,\n): Promise<MigrationResult<Data>> {\n const latestVersion = steps.length;\n const { version, data: initialData } = getVersionAndData(state);\n\n if (version < 0) {\n throw new Error(\n `State version ${version} is invalid; it cannot be negative`,\n );\n }\n\n if (version > latestVersion) {\n throw new Error(\n `State version ${version} is newer than the latest migration version ${latestVersion}`,\n );\n }\n\n const pendingSteps = steps.slice(version);\n let data: JsonObject = initialData;\n\n for (const step of pendingSteps) {\n assert(data, step.inputSchema ?? JsonObjectStruct);\n data = await step.migrate(data);\n assert(data, step.outputSchema ?? JsonObjectStruct);\n }\n\n return {\n version: latestVersion,\n state: {\n ...data,\n version: latestVersion,\n },\n migrated: pendingSteps.length > 0,\n } as MigrationResult<Data>;\n}\n\n/**\n * Build a {@link MigrationChain} wrapping the given internal steps.\n *\n * @param steps - The steps accumulated so far.\n * @returns A chain exposing `add`, `version`, and `apply`.\n */\nfunction buildChain<Data extends JsonObject>(\n steps: readonly MigrationStep[],\n): MigrationChain<Data> {\n return {\n version: steps.length,\n add: <Input extends Data, Output extends JsonObject>(\n step: MigrationStep<Input, Output>,\n ) => buildChain<Output>([...steps, step as unknown as MigrationStep]),\n apply: async (state) => applySteps<Data>(steps, state),\n };\n}\n\n/**\n * Start a new, empty migration chain.\n *\n * @example\n * ```typescript\n * const migrations = createMigrations()\n * .add({ migrate: (data) => ({ count: data.numberOfItems }) })\n * .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast\n *\n * const { state, migrated } = await migrations.apply(serializedState);\n * // Persist `state`; re-save if `migrated` is true.\n * ```\n * @returns An empty chain typed to `JsonObject`.\n */\nexport function createMigrations(): MigrationChain<JsonObject> {\n return buildChain<JsonObject>([]);\n}\n"]}
|
package/dist/migration.d.cts
CHANGED
|
@@ -1,64 +1,94 @@
|
|
|
1
1
|
import type { Infer, Struct } from "@metamask/superstruct";
|
|
2
2
|
import type { Json } from "@metamask/utils";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Constraint for flat migration state: must be a plain JSON object so that the
|
|
5
|
+
* `version` field can be inlined alongside its other fields.
|
|
6
|
+
*/
|
|
7
|
+
export type JsonObject = Record<string, Json>;
|
|
8
|
+
/**
|
|
9
|
+
* Superstruct schema for a plain JSON object (`Record<string, Json>`).
|
|
10
|
+
*
|
|
11
|
+
* Used as the default fallback schema for step input/output validation when no
|
|
12
|
+
* explicit schema is provided.
|
|
13
|
+
*/
|
|
14
|
+
export declare const JsonObjectStruct: Struct<JsonObject>;
|
|
15
|
+
/**
|
|
16
|
+
* Superstruct schema for an exact `{ version: integer }` object with no other
|
|
17
|
+
* fields.
|
|
18
|
+
*
|
|
19
|
+
* Use this struct when you need to validate a pure version token. To check
|
|
20
|
+
* whether arbitrary flat state carries a version field, use
|
|
21
|
+
* {@link isVersionedState} instead.
|
|
5
22
|
*/
|
|
6
23
|
export declare const VersionedStateStruct: Struct<{
|
|
7
|
-
data: Json;
|
|
8
24
|
version: number;
|
|
9
25
|
}, {
|
|
10
26
|
version: Struct<number, null>;
|
|
11
|
-
data: Struct<Json, unknown>;
|
|
12
27
|
}>;
|
|
13
28
|
/**
|
|
14
|
-
*
|
|
29
|
+
* Flat versioned state: the keyring state with `version` inlined alongside its
|
|
30
|
+
* own fields.
|
|
31
|
+
*
|
|
32
|
+
* This is the format that `serialize()` should produce and `deserialize()` will
|
|
33
|
+
* consume. The `version` field is managed by the migration framework; every
|
|
34
|
+
* other field belongs to the keyring.
|
|
15
35
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
36
|
+
* Do not use `version` or `migrated` as field names in keyring state, as the
|
|
37
|
+
* framework writes both at the top level when constructing a
|
|
38
|
+
* {@link MigrationResult}.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const state: VersionedState<{ accounts: string[] }> = {
|
|
42
|
+
* version: 3,
|
|
43
|
+
* accounts: [],
|
|
44
|
+
* };
|
|
19
45
|
*/
|
|
20
|
-
export type VersionedState<Data extends
|
|
21
|
-
data: Data;
|
|
22
|
-
};
|
|
46
|
+
export type VersionedState<Data extends JsonObject = JsonObject> = Data & Infer<typeof VersionedStateStruct>;
|
|
23
47
|
/**
|
|
24
48
|
* Return value of {@link MigrationChain.apply}.
|
|
25
49
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* written to storage, even when no other state change happens in the session.
|
|
50
|
+
* `state` is the flat {@link VersionedState} to persist. `migrated` is a
|
|
51
|
+
* runtime-only flag that is never written to storage. `version` duplicates
|
|
52
|
+
* `state.version` for convenience.
|
|
30
53
|
*/
|
|
31
|
-
export type MigrationResult<Data extends
|
|
54
|
+
export type MigrationResult<Data extends JsonObject = JsonObject> = {
|
|
55
|
+
/** Convenience alias for `state.version`. */
|
|
56
|
+
version: number;
|
|
57
|
+
/** The migrated flat state to persist. */
|
|
58
|
+
state: VersionedState<Data>;
|
|
59
|
+
/** `true` when at least one migration step was applied during this call. */
|
|
32
60
|
migrated: boolean;
|
|
33
61
|
};
|
|
34
62
|
/**
|
|
35
|
-
* Type guard to check if a value is a {@link VersionedState}
|
|
63
|
+
* Type guard to check if a value is a {@link VersionedState}.
|
|
64
|
+
*
|
|
65
|
+
* Returns `true` for any plain object that carries a `version: integer` field,
|
|
66
|
+
* regardless of what other fields are present.
|
|
36
67
|
*
|
|
37
68
|
* @param state - The value to check.
|
|
38
|
-
* @returns `true` if the value
|
|
69
|
+
* @returns `true` if the value has a `version: integer` field.
|
|
39
70
|
*/
|
|
40
|
-
export declare function isVersionedState<State extends
|
|
71
|
+
export declare function isVersionedState<State extends JsonObject = JsonObject>(state: State | VersionedState<State>): state is VersionedState<State>;
|
|
41
72
|
/**
|
|
42
73
|
* A single migration step, added to a {@link MigrationChain} via `.add()`.
|
|
43
74
|
*
|
|
44
|
-
* `Input` is bound automatically to the chain's current data type when the
|
|
45
|
-
* passed to `.add()`, so `migrate` receives a correctly typed argument
|
|
46
|
-
* cast.
|
|
75
|
+
* `Input` is bound automatically to the chain's current data type when the
|
|
76
|
+
* step is passed to `.add()`, so `migrate` receives a correctly typed argument
|
|
77
|
+
* with no manual cast.
|
|
47
78
|
*/
|
|
48
|
-
export type MigrationStep<Input extends
|
|
79
|
+
export type MigrationStep<Input extends JsonObject = JsonObject, Output extends JsonObject = JsonObject> = {
|
|
49
80
|
/**
|
|
50
81
|
* Transform state from the previous step's output to this step's output.
|
|
51
82
|
*
|
|
52
|
-
* Receives the raw inner data (not the versioned envelope). May be sync or
|
|
53
|
-
* support complex operations like re-deriving data from secrets.
|
|
83
|
+
* Receives the raw inner data (not the versioned envelope). May be sync or
|
|
84
|
+
* async to support complex operations like re-deriving data from secrets.
|
|
54
85
|
*
|
|
55
86
|
* @param data - The state data from the previous step.
|
|
56
87
|
* @returns The migrated data.
|
|
57
88
|
*/
|
|
58
89
|
migrate(data: Input): Output | Promise<Output>;
|
|
59
90
|
/**
|
|
60
|
-
* Optional schema validating this step's input before `migrate` is called.
|
|
61
|
-
* to a generic JSON-shape check when omitted.
|
|
91
|
+
* Optional schema validating this step's input before `migrate` is called.
|
|
62
92
|
*/
|
|
63
93
|
inputSchema?: Struct<Input>;
|
|
64
94
|
/**
|
|
@@ -67,43 +97,40 @@ export type MigrationStep<Input extends Json = Json, Output extends Json = Json>
|
|
|
67
97
|
outputSchema?: Struct<Output>;
|
|
68
98
|
};
|
|
69
99
|
/**
|
|
70
|
-
* A chain of migration steps for evolving keyring serialized state across
|
|
100
|
+
* A chain of migration steps for evolving keyring serialized state across
|
|
101
|
+
* versions.
|
|
71
102
|
*
|
|
72
|
-
* Steps are versioned by position: the first `.add()` call produces version 1,
|
|
73
|
-
* second version 2, and so on. Create one with {@link createMigrations}.
|
|
103
|
+
* Steps are versioned by position: the first `.add()` call produces version 1,
|
|
104
|
+
* the second version 2, and so on. Create one with {@link createMigrations}.
|
|
74
105
|
*/
|
|
75
|
-
export type MigrationChain<Data extends
|
|
106
|
+
export type MigrationChain<Data extends JsonObject = JsonObject> = {
|
|
76
107
|
/**
|
|
77
|
-
* The number of steps added so far.
|
|
78
|
-
*
|
|
108
|
+
* The number of steps added so far. Also the latest version number (use it
|
|
109
|
+
* in `serialize()`).
|
|
79
110
|
*/
|
|
80
111
|
readonly version: number;
|
|
81
112
|
/**
|
|
82
113
|
* Append a step to the chain.
|
|
83
114
|
*
|
|
84
|
-
* Returns a new chain typed to `Output`.
|
|
85
|
-
* so branching from a shared base chain is safe.
|
|
86
|
-
*
|
|
87
|
-
* `Input` defaults to the chain's current `Data` type. Providing `inputSchema` lets
|
|
88
|
-
* TypeScript infer a narrower `Input` (bounded to extend `Data`), so `migrate`
|
|
89
|
-
* receives a schema-typed argument when narrowing raw state into a specific shape,
|
|
90
|
-
* typically on a chain's first step, where `Data` is `Json`.
|
|
115
|
+
* Returns a new chain typed to `Output`. Does not mutate the chain it is
|
|
116
|
+
* called on, so branching from a shared base chain is safe.
|
|
91
117
|
*
|
|
92
118
|
* @param step - The migration step to append.
|
|
93
119
|
* @returns A new chain whose data type is the step's `Output`.
|
|
94
120
|
*/
|
|
95
|
-
add<Input extends Data = Data, Output extends
|
|
121
|
+
add<Input extends Data = Data, Output extends JsonObject = JsonObject>(step: MigrationStep<Input, Output>): MigrationChain<Output>;
|
|
96
122
|
/**
|
|
97
123
|
* Apply all pending steps to `state`.
|
|
98
124
|
*
|
|
99
|
-
* Handles both versioned state (
|
|
100
|
-
*
|
|
125
|
+
* Handles both flat versioned state (with a `version` field) and unversioned
|
|
126
|
+
* legacy state (plain object without `version`, treated as version 0).
|
|
101
127
|
*
|
|
102
|
-
* @param state - The serialized keyring state (from vault or previous
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* @throws If `state`
|
|
106
|
-
* step's `inputSchema`/`outputSchema`
|
|
128
|
+
* @param state - The serialized keyring state (from vault or previous
|
|
129
|
+
* serialize).
|
|
130
|
+
* @returns The migrated state wrapped in a {@link MigrationResult}.
|
|
131
|
+
* @throws If `state` is not a plain object, if its version is newer than the
|
|
132
|
+
* chain's latest version, or if a step's `inputSchema`/`outputSchema`
|
|
133
|
+
* validation fails.
|
|
107
134
|
*/
|
|
108
135
|
apply(state: Json): Promise<MigrationResult<Data>>;
|
|
109
136
|
};
|
|
@@ -116,9 +143,10 @@ export type MigrationChain<Data extends Json = Json> = {
|
|
|
116
143
|
* .add({ migrate: (data) => ({ count: data.numberOfItems }) })
|
|
117
144
|
* .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast
|
|
118
145
|
*
|
|
119
|
-
* const {
|
|
146
|
+
* const { state, migrated } = await migrations.apply(serializedState);
|
|
147
|
+
* // Persist `state`; re-save if `migrated` is true.
|
|
120
148
|
* ```
|
|
121
|
-
* @returns An empty chain
|
|
149
|
+
* @returns An empty chain typed to `JsonObject`.
|
|
122
150
|
*/
|
|
123
|
-
export declare function createMigrations(): MigrationChain<
|
|
151
|
+
export declare function createMigrations(): MigrationChain<JsonObject>;
|
|
124
152
|
//# sourceMappingURL=migration.d.cts.map
|
package/dist/migration.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.d.cts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B;
|
|
1
|
+
{"version":3,"file":"migration.d.cts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B;AAW3D,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAE5C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAE9C;;;;;GAKG;AAIH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAK/C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;EAE/B,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,IAAI,IAAI,GACrE,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,IAAI;IAClE,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAYF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACpE,KAAK,EAAE,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GACnC,KAAK,IAAI,cAAc,CAAC,KAAK,CAAC,CAEhC;AAyBD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,CACvB,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,MAAM,SAAS,UAAU,GAAG,UAAU,IACpC;IACF;;;;;;;;OAQG;IACH,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,IAAI;IACjE;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,GAAG,CAAC,KAAK,SAAS,IAAI,GAAG,IAAI,EAAE,MAAM,SAAS,UAAU,GAAG,UAAU,EACnE,IAAI,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,GACjC,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;CACpD,CAAC;AAmEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,CAAC,UAAU,CAAC,CAE7D"}
|
package/dist/migration.d.mts
CHANGED
|
@@ -1,64 +1,94 @@
|
|
|
1
1
|
import type { Infer, Struct } from "@metamask/superstruct";
|
|
2
2
|
import type { Json } from "@metamask/utils";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Constraint for flat migration state: must be a plain JSON object so that the
|
|
5
|
+
* `version` field can be inlined alongside its other fields.
|
|
6
|
+
*/
|
|
7
|
+
export type JsonObject = Record<string, Json>;
|
|
8
|
+
/**
|
|
9
|
+
* Superstruct schema for a plain JSON object (`Record<string, Json>`).
|
|
10
|
+
*
|
|
11
|
+
* Used as the default fallback schema for step input/output validation when no
|
|
12
|
+
* explicit schema is provided.
|
|
13
|
+
*/
|
|
14
|
+
export declare const JsonObjectStruct: Struct<JsonObject>;
|
|
15
|
+
/**
|
|
16
|
+
* Superstruct schema for an exact `{ version: integer }` object with no other
|
|
17
|
+
* fields.
|
|
18
|
+
*
|
|
19
|
+
* Use this struct when you need to validate a pure version token. To check
|
|
20
|
+
* whether arbitrary flat state carries a version field, use
|
|
21
|
+
* {@link isVersionedState} instead.
|
|
5
22
|
*/
|
|
6
23
|
export declare const VersionedStateStruct: Struct<{
|
|
7
|
-
data: Json;
|
|
8
24
|
version: number;
|
|
9
25
|
}, {
|
|
10
26
|
version: Struct<number, null>;
|
|
11
|
-
data: Struct<Json, unknown>;
|
|
12
27
|
}>;
|
|
13
28
|
/**
|
|
14
|
-
*
|
|
29
|
+
* Flat versioned state: the keyring state with `version` inlined alongside its
|
|
30
|
+
* own fields.
|
|
31
|
+
*
|
|
32
|
+
* This is the format that `serialize()` should produce and `deserialize()` will
|
|
33
|
+
* consume. The `version` field is managed by the migration framework; every
|
|
34
|
+
* other field belongs to the keyring.
|
|
15
35
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
36
|
+
* Do not use `version` or `migrated` as field names in keyring state, as the
|
|
37
|
+
* framework writes both at the top level when constructing a
|
|
38
|
+
* {@link MigrationResult}.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const state: VersionedState<{ accounts: string[] }> = {
|
|
42
|
+
* version: 3,
|
|
43
|
+
* accounts: [],
|
|
44
|
+
* };
|
|
19
45
|
*/
|
|
20
|
-
export type VersionedState<Data extends
|
|
21
|
-
data: Data;
|
|
22
|
-
};
|
|
46
|
+
export type VersionedState<Data extends JsonObject = JsonObject> = Data & Infer<typeof VersionedStateStruct>;
|
|
23
47
|
/**
|
|
24
48
|
* Return value of {@link MigrationChain.apply}.
|
|
25
49
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* written to storage, even when no other state change happens in the session.
|
|
50
|
+
* `state` is the flat {@link VersionedState} to persist. `migrated` is a
|
|
51
|
+
* runtime-only flag that is never written to storage. `version` duplicates
|
|
52
|
+
* `state.version` for convenience.
|
|
30
53
|
*/
|
|
31
|
-
export type MigrationResult<Data extends
|
|
54
|
+
export type MigrationResult<Data extends JsonObject = JsonObject> = {
|
|
55
|
+
/** Convenience alias for `state.version`. */
|
|
56
|
+
version: number;
|
|
57
|
+
/** The migrated flat state to persist. */
|
|
58
|
+
state: VersionedState<Data>;
|
|
59
|
+
/** `true` when at least one migration step was applied during this call. */
|
|
32
60
|
migrated: boolean;
|
|
33
61
|
};
|
|
34
62
|
/**
|
|
35
|
-
* Type guard to check if a value is a {@link VersionedState}
|
|
63
|
+
* Type guard to check if a value is a {@link VersionedState}.
|
|
64
|
+
*
|
|
65
|
+
* Returns `true` for any plain object that carries a `version: integer` field,
|
|
66
|
+
* regardless of what other fields are present.
|
|
36
67
|
*
|
|
37
68
|
* @param state - The value to check.
|
|
38
|
-
* @returns `true` if the value
|
|
69
|
+
* @returns `true` if the value has a `version: integer` field.
|
|
39
70
|
*/
|
|
40
|
-
export declare function isVersionedState<State extends
|
|
71
|
+
export declare function isVersionedState<State extends JsonObject = JsonObject>(state: State | VersionedState<State>): state is VersionedState<State>;
|
|
41
72
|
/**
|
|
42
73
|
* A single migration step, added to a {@link MigrationChain} via `.add()`.
|
|
43
74
|
*
|
|
44
|
-
* `Input` is bound automatically to the chain's current data type when the
|
|
45
|
-
* passed to `.add()`, so `migrate` receives a correctly typed argument
|
|
46
|
-
* cast.
|
|
75
|
+
* `Input` is bound automatically to the chain's current data type when the
|
|
76
|
+
* step is passed to `.add()`, so `migrate` receives a correctly typed argument
|
|
77
|
+
* with no manual cast.
|
|
47
78
|
*/
|
|
48
|
-
export type MigrationStep<Input extends
|
|
79
|
+
export type MigrationStep<Input extends JsonObject = JsonObject, Output extends JsonObject = JsonObject> = {
|
|
49
80
|
/**
|
|
50
81
|
* Transform state from the previous step's output to this step's output.
|
|
51
82
|
*
|
|
52
|
-
* Receives the raw inner data (not the versioned envelope). May be sync or
|
|
53
|
-
* support complex operations like re-deriving data from secrets.
|
|
83
|
+
* Receives the raw inner data (not the versioned envelope). May be sync or
|
|
84
|
+
* async to support complex operations like re-deriving data from secrets.
|
|
54
85
|
*
|
|
55
86
|
* @param data - The state data from the previous step.
|
|
56
87
|
* @returns The migrated data.
|
|
57
88
|
*/
|
|
58
89
|
migrate(data: Input): Output | Promise<Output>;
|
|
59
90
|
/**
|
|
60
|
-
* Optional schema validating this step's input before `migrate` is called.
|
|
61
|
-
* to a generic JSON-shape check when omitted.
|
|
91
|
+
* Optional schema validating this step's input before `migrate` is called.
|
|
62
92
|
*/
|
|
63
93
|
inputSchema?: Struct<Input>;
|
|
64
94
|
/**
|
|
@@ -67,43 +97,40 @@ export type MigrationStep<Input extends Json = Json, Output extends Json = Json>
|
|
|
67
97
|
outputSchema?: Struct<Output>;
|
|
68
98
|
};
|
|
69
99
|
/**
|
|
70
|
-
* A chain of migration steps for evolving keyring serialized state across
|
|
100
|
+
* A chain of migration steps for evolving keyring serialized state across
|
|
101
|
+
* versions.
|
|
71
102
|
*
|
|
72
|
-
* Steps are versioned by position: the first `.add()` call produces version 1,
|
|
73
|
-
* second version 2, and so on. Create one with {@link createMigrations}.
|
|
103
|
+
* Steps are versioned by position: the first `.add()` call produces version 1,
|
|
104
|
+
* the second version 2, and so on. Create one with {@link createMigrations}.
|
|
74
105
|
*/
|
|
75
|
-
export type MigrationChain<Data extends
|
|
106
|
+
export type MigrationChain<Data extends JsonObject = JsonObject> = {
|
|
76
107
|
/**
|
|
77
|
-
* The number of steps added so far.
|
|
78
|
-
*
|
|
108
|
+
* The number of steps added so far. Also the latest version number (use it
|
|
109
|
+
* in `serialize()`).
|
|
79
110
|
*/
|
|
80
111
|
readonly version: number;
|
|
81
112
|
/**
|
|
82
113
|
* Append a step to the chain.
|
|
83
114
|
*
|
|
84
|
-
* Returns a new chain typed to `Output`.
|
|
85
|
-
* so branching from a shared base chain is safe.
|
|
86
|
-
*
|
|
87
|
-
* `Input` defaults to the chain's current `Data` type. Providing `inputSchema` lets
|
|
88
|
-
* TypeScript infer a narrower `Input` (bounded to extend `Data`), so `migrate`
|
|
89
|
-
* receives a schema-typed argument when narrowing raw state into a specific shape,
|
|
90
|
-
* typically on a chain's first step, where `Data` is `Json`.
|
|
115
|
+
* Returns a new chain typed to `Output`. Does not mutate the chain it is
|
|
116
|
+
* called on, so branching from a shared base chain is safe.
|
|
91
117
|
*
|
|
92
118
|
* @param step - The migration step to append.
|
|
93
119
|
* @returns A new chain whose data type is the step's `Output`.
|
|
94
120
|
*/
|
|
95
|
-
add<Input extends Data = Data, Output extends
|
|
121
|
+
add<Input extends Data = Data, Output extends JsonObject = JsonObject>(step: MigrationStep<Input, Output>): MigrationChain<Output>;
|
|
96
122
|
/**
|
|
97
123
|
* Apply all pending steps to `state`.
|
|
98
124
|
*
|
|
99
|
-
* Handles both versioned state (
|
|
100
|
-
*
|
|
125
|
+
* Handles both flat versioned state (with a `version` field) and unversioned
|
|
126
|
+
* legacy state (plain object without `version`, treated as version 0).
|
|
101
127
|
*
|
|
102
|
-
* @param state - The serialized keyring state (from vault or previous
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* @throws If `state`
|
|
106
|
-
* step's `inputSchema`/`outputSchema`
|
|
128
|
+
* @param state - The serialized keyring state (from vault or previous
|
|
129
|
+
* serialize).
|
|
130
|
+
* @returns The migrated state wrapped in a {@link MigrationResult}.
|
|
131
|
+
* @throws If `state` is not a plain object, if its version is newer than the
|
|
132
|
+
* chain's latest version, or if a step's `inputSchema`/`outputSchema`
|
|
133
|
+
* validation fails.
|
|
107
134
|
*/
|
|
108
135
|
apply(state: Json): Promise<MigrationResult<Data>>;
|
|
109
136
|
};
|
|
@@ -116,9 +143,10 @@ export type MigrationChain<Data extends Json = Json> = {
|
|
|
116
143
|
* .add({ migrate: (data) => ({ count: data.numberOfItems }) })
|
|
117
144
|
* .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast
|
|
118
145
|
*
|
|
119
|
-
* const {
|
|
146
|
+
* const { state, migrated } = await migrations.apply(serializedState);
|
|
147
|
+
* // Persist `state`; re-save if `migrated` is true.
|
|
120
148
|
* ```
|
|
121
|
-
* @returns An empty chain
|
|
149
|
+
* @returns An empty chain typed to `JsonObject`.
|
|
122
150
|
*/
|
|
123
|
-
export declare function createMigrations(): MigrationChain<
|
|
151
|
+
export declare function createMigrations(): MigrationChain<JsonObject>;
|
|
124
152
|
//# sourceMappingURL=migration.d.mts.map
|
package/dist/migration.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.d.mts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B;
|
|
1
|
+
{"version":3,"file":"migration.d.mts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B;AAW3D,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAE5C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAE9C;;;;;GAKG;AAIH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAK/C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;EAE/B,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,IAAI,IAAI,GACrE,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,IAAI;IAClE,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAYF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACpE,KAAK,EAAE,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,GACnC,KAAK,IAAI,cAAc,CAAC,KAAK,CAAC,CAEhC;AAyBD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,CACvB,KAAK,SAAS,UAAU,GAAG,UAAU,EACrC,MAAM,SAAS,UAAU,GAAG,UAAU,IACpC;IACF;;;;;;;;OAQG;IACH,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,IAAI;IACjE;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,GAAG,CAAC,KAAK,SAAS,IAAI,GAAG,IAAI,EAAE,MAAM,SAAS,UAAU,GAAG,UAAU,EACnE,IAAI,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,GACjC,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;CACpD,CAAC;AAmEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,CAAC,UAAU,CAAC,CAE7D"}
|
package/dist/migration.mjs
CHANGED
|
@@ -1,31 +1,66 @@
|
|
|
1
|
-
import { assert, integer, is,
|
|
1
|
+
import { assert, integer, is, record, refine, string, type } from "@metamask/superstruct";
|
|
2
2
|
import { JsonStruct } from "@metamask/utils";
|
|
3
3
|
/**
|
|
4
|
-
* Superstruct schema for
|
|
4
|
+
* Superstruct schema for a plain JSON object (`Record<string, Json>`).
|
|
5
|
+
*
|
|
6
|
+
* Used as the default fallback schema for step input/output validation when no
|
|
7
|
+
* explicit schema is provided.
|
|
8
|
+
*/
|
|
9
|
+
// `record(string(), JsonStruct)` alone would accept arrays: `typeof [] === 'object'`
|
|
10
|
+
// satisfies superstruct's `isObject` check, and `Object.entries(['a', 'b'])` yields
|
|
11
|
+
// valid string keys with JSON values. The refinement makes the array rejection explicit.
|
|
12
|
+
export const JsonObjectStruct = refine(record(string(), JsonStruct), 'JsonObject', (value) => typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
13
|
+
/**
|
|
14
|
+
* Superstruct schema for an exact `{ version: integer }` object with no other
|
|
15
|
+
* fields.
|
|
16
|
+
*
|
|
17
|
+
* Use this struct when you need to validate a pure version token. To check
|
|
18
|
+
* whether arbitrary flat state carries a version field, use
|
|
19
|
+
* {@link isVersionedState} instead.
|
|
5
20
|
*/
|
|
6
|
-
export const VersionedStateStruct =
|
|
21
|
+
export const VersionedStateStruct = type({
|
|
7
22
|
version: integer(),
|
|
8
|
-
data: JsonStruct,
|
|
9
23
|
});
|
|
10
24
|
/**
|
|
11
|
-
* Type guard to check if a value is a
|
|
25
|
+
* Type guard to check if a value is a plain JSON object.
|
|
26
|
+
*
|
|
27
|
+
* @param value - The value to check.
|
|
28
|
+
* @returns `true` if the value is a non-null, non-array object.
|
|
29
|
+
*/
|
|
30
|
+
function isJsonObject(value) {
|
|
31
|
+
return is(value, JsonObjectStruct);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Type guard to check if a value is a {@link VersionedState}.
|
|
35
|
+
*
|
|
36
|
+
* Returns `true` for any plain object that carries a `version: integer` field,
|
|
37
|
+
* regardless of what other fields are present.
|
|
12
38
|
*
|
|
13
39
|
* @param state - The value to check.
|
|
14
|
-
* @returns `true` if the value
|
|
40
|
+
* @returns `true` if the value has a `version: integer` field.
|
|
15
41
|
*/
|
|
16
42
|
export function isVersionedState(state) {
|
|
17
43
|
return is(state, VersionedStateStruct);
|
|
18
44
|
}
|
|
19
45
|
/**
|
|
20
|
-
*
|
|
46
|
+
* Extract the version and inner data from state.
|
|
21
47
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
48
|
+
* For versioned state (`isVersionedState` returns true), strips the `version`
|
|
49
|
+
* field and returns the rest as data. For unversioned legacy state (plain
|
|
50
|
+
* object with no `version` field), returns version 0 and the object as-is.
|
|
51
|
+
*
|
|
52
|
+
* @param state - The state to decompose.
|
|
53
|
+
* @returns The version number and the inner data object.
|
|
24
54
|
*/
|
|
25
55
|
function getVersionAndData(state) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
56
|
+
if (!isJsonObject(state)) {
|
|
57
|
+
throw new Error('Unversioned state must be a plain object');
|
|
58
|
+
}
|
|
59
|
+
if (isVersionedState(state)) {
|
|
60
|
+
const { version, ...data } = state;
|
|
61
|
+
return { version, data };
|
|
62
|
+
}
|
|
63
|
+
return { version: 0, data: state };
|
|
29
64
|
}
|
|
30
65
|
/**
|
|
31
66
|
* Apply the pending steps of a chain to `state`.
|
|
@@ -34,7 +69,7 @@ function getVersionAndData(state) {
|
|
|
34
69
|
*
|
|
35
70
|
* @param steps - All steps of the chain.
|
|
36
71
|
* @param state - The serialized keyring state.
|
|
37
|
-
* @returns The migrated state
|
|
72
|
+
* @returns The migrated state in a {@link MigrationResult}.
|
|
38
73
|
*/
|
|
39
74
|
async function applySteps(steps, state) {
|
|
40
75
|
const latestVersion = steps.length;
|
|
@@ -48,13 +83,16 @@ async function applySteps(steps, state) {
|
|
|
48
83
|
const pendingSteps = steps.slice(version);
|
|
49
84
|
let data = initialData;
|
|
50
85
|
for (const step of pendingSteps) {
|
|
51
|
-
assert(data, step.inputSchema ??
|
|
86
|
+
assert(data, step.inputSchema ?? JsonObjectStruct);
|
|
52
87
|
data = await step.migrate(data);
|
|
53
|
-
assert(data, step.outputSchema ??
|
|
88
|
+
assert(data, step.outputSchema ?? JsonObjectStruct);
|
|
54
89
|
}
|
|
55
90
|
return {
|
|
56
91
|
version: latestVersion,
|
|
57
|
-
|
|
92
|
+
state: {
|
|
93
|
+
...data,
|
|
94
|
+
version: latestVersion,
|
|
95
|
+
},
|
|
58
96
|
migrated: pendingSteps.length > 0,
|
|
59
97
|
};
|
|
60
98
|
}
|
|
@@ -80,9 +118,10 @@ function buildChain(steps) {
|
|
|
80
118
|
* .add({ migrate: (data) => ({ count: data.numberOfItems }) })
|
|
81
119
|
* .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast
|
|
82
120
|
*
|
|
83
|
-
* const {
|
|
121
|
+
* const { state, migrated } = await migrations.apply(serializedState);
|
|
122
|
+
* // Persist `state`; re-save if `migrated` is true.
|
|
84
123
|
* ```
|
|
85
|
-
* @returns An empty chain
|
|
124
|
+
* @returns An empty chain typed to `JsonObject`.
|
|
86
125
|
*/
|
|
87
126
|
export function createMigrations() {
|
|
88
127
|
return buildChain([]);
|
package/dist/migration.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.mjs","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,8BAA8B;AACpE,OAAO,EAAE,UAAU,EAAE,wBAAwB;AAG7C;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;IACzC,OAAO,EAAE,OAAO,EAAE;IAClB,IAAI,EAAE,UAAU;CACjB,CAAC,CAAC;AA4BH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAoC;IAEpC,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,KAAoC;IAEpC,OAAO,gBAAgB,CAAC,KAAK,CAAC;QAC5B,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;QAC9C,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAClC,CAAC;AA8ED;;;;;;;;GAQG;AACH,KAAK,UAAU,UAAU,CACvB,KAA+B,EAC/B,KAAW;IAEX,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAEhE,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,oCAAoC,CAC7D,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,+CAA+C,aAAa,EAAE,CACvF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,GAAG,WAAW,CAAC;IAEvB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,UAAU,CAAC,CAAC;QAC7C,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,UAAU,CAAC,CAAC;IAChD,CAAC;IAED,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,IAAI;QACJ,QAAQ,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC;KACT,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CACjB,KAA+B;IAE/B,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,MAAM;QACrB,GAAG,EAAE,CACH,IAAkC,EAClC,EAAE,CAAC,UAAU,CAAS,CAAC,GAAG,KAAK,EAAE,IAAgC,CAAC,CAAC;QACrE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAO,KAAK,EAAE,KAAK,CAAC;KACvD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAO,EAAE,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import type { Infer, Struct } from '@metamask/superstruct';\nimport { assert, integer, is, object } from '@metamask/superstruct';\nimport { JsonStruct } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\n\n/**\n * Superstruct schema for the versioned state envelope.\n */\nexport const VersionedStateStruct = object({\n version: integer(),\n data: JsonStruct,\n});\n\n/**\n * Versioned state envelope wrapping the actual keyring data.\n *\n * After migrations are applied, state is always wrapped in this format. `serialize()`\n * should produce this envelope so that subsequent `deserialize()` calls can detect the\n * version.\n */\nexport type VersionedState<Data extends Json = Json> = Omit<\n Infer<typeof VersionedStateStruct>,\n 'data'\n> & {\n data: Data;\n};\n\n/**\n * Return value of {@link MigrationChain.apply}.\n *\n * Extends {@link VersionedState} with a `migrated` flag that is `true` when at least\n * one step was applied during the call. Callers can use this to detect that the\n * in-memory state has been upgraded and schedule a persist so the new version is\n * written to storage, even when no other state change happens in the session.\n */\nexport type MigrationResult<Data extends Json = Json> = VersionedState<Data> & {\n migrated: boolean;\n};\n\n/**\n * Type guard to check if a value is a {@link VersionedState} envelope.\n *\n * @param state - The value to check.\n * @returns `true` if the value is a versioned state envelope.\n */\nexport function isVersionedState<State extends Json = Json>(\n state: State | VersionedState<State>,\n): state is VersionedState<State> {\n return is(state, VersionedStateStruct);\n}\n\n/**\n * Get the version and data from state, treating unversioned state as version 0.\n *\n * @param state - The state to check.\n * @returns The version and data.\n */\nfunction getVersionAndData<State extends Json = Json>(\n state: State | VersionedState<State>,\n): VersionedState<State> {\n return isVersionedState(state)\n ? { version: state.version, data: state.data }\n : { version: 0, data: state };\n}\n\n/**\n * A single migration step, added to a {@link MigrationChain} via `.add()`.\n *\n * `Input` is bound automatically to the chain's current data type when the step is\n * passed to `.add()`, so `migrate` receives a correctly typed argument with no manual\n * cast.\n */\nexport type MigrationStep<\n Input extends Json = Json,\n Output extends Json = Json,\n> = {\n /**\n * Transform state from the previous step's output to this step's output.\n *\n * Receives the raw inner data (not the versioned envelope). May be sync or async to\n * support complex operations like re-deriving data from secrets.\n *\n * @param data - The state data from the previous step.\n * @returns The migrated data.\n */\n migrate(data: Input): Output | Promise<Output>;\n /**\n * Optional schema validating this step's input before `migrate` is called. Defaults\n * to a generic JSON-shape check when omitted.\n */\n inputSchema?: Struct<Input>;\n /**\n * Optional schema validating this step's output at runtime.\n */\n outputSchema?: Struct<Output>;\n};\n\n/**\n * A chain of migration steps for evolving keyring serialized state across versions.\n *\n * Steps are versioned by position: the first `.add()` call produces version 1, the\n * second version 2, and so on. Create one with {@link createMigrations}.\n */\nexport type MigrationChain<Data extends Json = Json> = {\n /**\n * The number of steps added so far. Since steps are versioned by position starting at\n * 1, this also doubles as \"the latest version\" (use it in `serialize()`).\n */\n readonly version: number;\n /**\n * Append a step to the chain.\n *\n * Returns a new chain typed to `Output`. It does not mutate the chain it's called on,\n * so branching from a shared base chain is safe.\n *\n * `Input` defaults to the chain's current `Data` type. Providing `inputSchema` lets\n * TypeScript infer a narrower `Input` (bounded to extend `Data`), so `migrate`\n * receives a schema-typed argument when narrowing raw state into a specific shape,\n * typically on a chain's first step, where `Data` is `Json`.\n *\n * @param step - The migration step to append.\n * @returns A new chain whose data type is the step's `Output`.\n */\n add<Input extends Data = Data, Output extends Json = Json>(\n step: MigrationStep<Input, Output>,\n ): MigrationChain<Output>;\n /**\n * Apply all pending steps to `state`.\n *\n * Handles both versioned state (wrapped in `{ version, data }` envelope) and\n * unversioned legacy state (treated as version 0).\n *\n * @param state - The serialized keyring state (from vault or previous serialize).\n * @returns The migrated state wrapped in a versioned envelope, plus a `migrated`\n * flag.\n * @throws If `state`'s version is newer than this chain's latest version, or if a\n * step's `inputSchema`/`outputSchema` validation fails.\n */\n apply(state: Json): Promise<MigrationResult<Data>>;\n};\n\n/**\n * Apply the pending steps of a chain to `state`.\n *\n * Implements {@link MigrationChain.apply} for the chain built from `steps`.\n *\n * @param steps - All steps of the chain.\n * @param state - The serialized keyring state.\n * @returns The migrated state wrapped in a versioned envelope, plus a `migrated` flag.\n */\nasync function applySteps<Data extends Json>(\n steps: readonly MigrationStep[],\n state: Json,\n): Promise<MigrationResult<Data>> {\n const latestVersion = steps.length;\n const { version, data: initialData } = getVersionAndData(state);\n\n if (version < 0) {\n throw new Error(\n `State version ${version} is invalid; it cannot be negative`,\n );\n }\n\n if (version > latestVersion) {\n throw new Error(\n `State version ${version} is newer than the latest migration version ${latestVersion}`,\n );\n }\n\n const pendingSteps = steps.slice(version);\n let data = initialData;\n\n for (const step of pendingSteps) {\n assert(data, step.inputSchema ?? JsonStruct);\n data = await step.migrate(data);\n assert(data, step.outputSchema ?? JsonStruct);\n }\n\n return {\n version: latestVersion,\n data,\n migrated: pendingSteps.length > 0,\n } as MigrationResult<Data>;\n}\n\n/**\n * Build a {@link MigrationChain} wrapping the given internal steps.\n *\n * @param steps - The steps accumulated so far.\n * @returns A chain exposing `add`, `version`, and `apply`.\n */\nfunction buildChain<Data extends Json>(\n steps: readonly MigrationStep[],\n): MigrationChain<Data> {\n return {\n version: steps.length,\n add: <Input extends Data, Output extends Json>(\n step: MigrationStep<Input, Output>,\n ) => buildChain<Output>([...steps, step as unknown as MigrationStep]),\n apply: async (state) => applySteps<Data>(steps, state),\n };\n}\n\n/**\n * Start a new, empty migration chain.\n *\n * @example\n * ```typescript\n * const migrations = createMigrations()\n * .add({ migrate: (data) => ({ count: data.numberOfItems }) })\n * .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast\n *\n * const { data } = await migrations.apply(state);\n * ```\n * @returns An empty chain, typed to accept `Json` as its first step's input.\n */\nexport function createMigrations(): MigrationChain<Json> {\n return buildChain<Json>([]);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"migration.mjs","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EACN,OAAO,EACP,EAAE,EACF,MAAM,EACN,MAAM,EACN,MAAM,EACN,IAAI,EACL,8BAA8B;AAC/B,OAAO,EAAE,UAAU,EAAE,wBAAwB;AAS7C;;;;;GAKG;AACH,qFAAqF;AACrF,oFAAoF;AACpF,yFAAyF;AACzF,MAAM,CAAC,MAAM,gBAAgB,GAAuB,MAAM,CACxD,MAAM,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,EAC5B,YAAY,EACZ,CAAC,KAAK,EAAE,EAAE,CACR,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CACvE,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;IACvC,OAAO,EAAE,OAAO,EAAE;CACnB,CAAC,CAAC;AAuCH;;;;;GAKG;AACH,SAAS,YAAY,CAAC,KAAW;IAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAoC;IAEpC,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,iBAAiB,CAAC,KAAW;IACpC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACrC,CAAC;AA0ED;;;;;;;;GAQG;AACH,KAAK,UAAU,UAAU,CACvB,KAA+B,EAC/B,KAAW;IAEX,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAEhE,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,oCAAoC,CAC7D,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,iBAAiB,OAAO,+CAA+C,aAAa,EAAE,CACvF,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,IAAI,GAAe,WAAW,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,gBAAgB,CAAC,CAAC;QACnD,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI,gBAAgB,CAAC,CAAC;IACtD,CAAC;IAED,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE;YACL,GAAG,IAAI;YACP,OAAO,EAAE,aAAa;SACvB;QACD,QAAQ,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC;KACT,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CACjB,KAA+B;IAE/B,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,MAAM;QACrB,GAAG,EAAE,CACH,IAAkC,EAClC,EAAE,CAAC,UAAU,CAAS,CAAC,GAAG,KAAK,EAAE,IAAgC,CAAC,CAAC;QACrE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAO,KAAK,EAAE,KAAK,CAAC;KACvD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAa,EAAE,CAAC,CAAC;AACpC,CAAC","sourcesContent":["import type { Infer, Struct } from '@metamask/superstruct';\nimport {\n assert,\n integer,\n is,\n record,\n refine,\n string,\n type,\n} from '@metamask/superstruct';\nimport { JsonStruct } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\n\n/**\n * Constraint for flat migration state: must be a plain JSON object so that the\n * `version` field can be inlined alongside its other fields.\n */\nexport type JsonObject = Record<string, Json>;\n\n/**\n * Superstruct schema for a plain JSON object (`Record<string, Json>`).\n *\n * Used as the default fallback schema for step input/output validation when no\n * explicit schema is provided.\n */\n// `record(string(), JsonStruct)` alone would accept arrays: `typeof [] === 'object'`\n// satisfies superstruct's `isObject` check, and `Object.entries(['a', 'b'])` yields\n// valid string keys with JSON values. The refinement makes the array rejection explicit.\nexport const JsonObjectStruct: Struct<JsonObject> = refine(\n record(string(), JsonStruct),\n 'JsonObject',\n (value) =>\n typeof value === 'object' && value !== null && !Array.isArray(value),\n);\n\n/**\n * Superstruct schema for an exact `{ version: integer }` object with no other\n * fields.\n *\n * Use this struct when you need to validate a pure version token. To check\n * whether arbitrary flat state carries a version field, use\n * {@link isVersionedState} instead.\n */\nexport const VersionedStateStruct = type({\n version: integer(),\n});\n\n/**\n * Flat versioned state: the keyring state with `version` inlined alongside its\n * own fields.\n *\n * This is the format that `serialize()` should produce and `deserialize()` will\n * consume. The `version` field is managed by the migration framework; every\n * other field belongs to the keyring.\n *\n * Do not use `version` or `migrated` as field names in keyring state, as the\n * framework writes both at the top level when constructing a\n * {@link MigrationResult}.\n *\n * @example\n * const state: VersionedState<{ accounts: string[] }> = {\n * version: 3,\n * accounts: [],\n * };\n */\nexport type VersionedState<Data extends JsonObject = JsonObject> = Data &\n Infer<typeof VersionedStateStruct>;\n\n/**\n * Return value of {@link MigrationChain.apply}.\n *\n * `state` is the flat {@link VersionedState} to persist. `migrated` is a\n * runtime-only flag that is never written to storage. `version` duplicates\n * `state.version` for convenience.\n */\nexport type MigrationResult<Data extends JsonObject = JsonObject> = {\n /** Convenience alias for `state.version`. */\n version: number;\n /** The migrated flat state to persist. */\n state: VersionedState<Data>;\n /** `true` when at least one migration step was applied during this call. */\n migrated: boolean;\n};\n\n/**\n * Type guard to check if a value is a plain JSON object.\n *\n * @param value - The value to check.\n * @returns `true` if the value is a non-null, non-array object.\n */\nfunction isJsonObject(value: Json): value is JsonObject {\n return is(value, JsonObjectStruct);\n}\n\n/**\n * Type guard to check if a value is a {@link VersionedState}.\n *\n * Returns `true` for any plain object that carries a `version: integer` field,\n * regardless of what other fields are present.\n *\n * @param state - The value to check.\n * @returns `true` if the value has a `version: integer` field.\n */\nexport function isVersionedState<State extends JsonObject = JsonObject>(\n state: State | VersionedState<State>,\n): state is VersionedState<State> {\n return is(state, VersionedStateStruct);\n}\n\n/**\n * Extract the version and inner data from state.\n *\n * For versioned state (`isVersionedState` returns true), strips the `version`\n * field and returns the rest as data. For unversioned legacy state (plain\n * object with no `version` field), returns version 0 and the object as-is.\n *\n * @param state - The state to decompose.\n * @returns The version number and the inner data object.\n */\nfunction getVersionAndData(state: Json): { version: number; data: JsonObject } {\n if (!isJsonObject(state)) {\n throw new Error('Unversioned state must be a plain object');\n }\n\n if (isVersionedState(state)) {\n const { version, ...data } = state;\n return { version, data };\n }\n\n return { version: 0, data: state };\n}\n\n/**\n * A single migration step, added to a {@link MigrationChain} via `.add()`.\n *\n * `Input` is bound automatically to the chain's current data type when the\n * step is passed to `.add()`, so `migrate` receives a correctly typed argument\n * with no manual cast.\n */\nexport type MigrationStep<\n Input extends JsonObject = JsonObject,\n Output extends JsonObject = JsonObject,\n> = {\n /**\n * Transform state from the previous step's output to this step's output.\n *\n * Receives the raw inner data (not the versioned envelope). May be sync or\n * async to support complex operations like re-deriving data from secrets.\n *\n * @param data - The state data from the previous step.\n * @returns The migrated data.\n */\n migrate(data: Input): Output | Promise<Output>;\n /**\n * Optional schema validating this step's input before `migrate` is called.\n */\n inputSchema?: Struct<Input>;\n /**\n * Optional schema validating this step's output at runtime.\n */\n outputSchema?: Struct<Output>;\n};\n\n/**\n * A chain of migration steps for evolving keyring serialized state across\n * versions.\n *\n * Steps are versioned by position: the first `.add()` call produces version 1,\n * the second version 2, and so on. Create one with {@link createMigrations}.\n */\nexport type MigrationChain<Data extends JsonObject = JsonObject> = {\n /**\n * The number of steps added so far. Also the latest version number (use it\n * in `serialize()`).\n */\n readonly version: number;\n /**\n * Append a step to the chain.\n *\n * Returns a new chain typed to `Output`. Does not mutate the chain it is\n * called on, so branching from a shared base chain is safe.\n *\n * @param step - The migration step to append.\n * @returns A new chain whose data type is the step's `Output`.\n */\n add<Input extends Data = Data, Output extends JsonObject = JsonObject>(\n step: MigrationStep<Input, Output>,\n ): MigrationChain<Output>;\n /**\n * Apply all pending steps to `state`.\n *\n * Handles both flat versioned state (with a `version` field) and unversioned\n * legacy state (plain object without `version`, treated as version 0).\n *\n * @param state - The serialized keyring state (from vault or previous\n * serialize).\n * @returns The migrated state wrapped in a {@link MigrationResult}.\n * @throws If `state` is not a plain object, if its version is newer than the\n * chain's latest version, or if a step's `inputSchema`/`outputSchema`\n * validation fails.\n */\n apply(state: Json): Promise<MigrationResult<Data>>;\n};\n\n/**\n * Apply the pending steps of a chain to `state`.\n *\n * Implements {@link MigrationChain.apply} for the chain built from `steps`.\n *\n * @param steps - All steps of the chain.\n * @param state - The serialized keyring state.\n * @returns The migrated state in a {@link MigrationResult}.\n */\nasync function applySteps<Data extends JsonObject>(\n steps: readonly MigrationStep[],\n state: Json,\n): Promise<MigrationResult<Data>> {\n const latestVersion = steps.length;\n const { version, data: initialData } = getVersionAndData(state);\n\n if (version < 0) {\n throw new Error(\n `State version ${version} is invalid; it cannot be negative`,\n );\n }\n\n if (version > latestVersion) {\n throw new Error(\n `State version ${version} is newer than the latest migration version ${latestVersion}`,\n );\n }\n\n const pendingSteps = steps.slice(version);\n let data: JsonObject = initialData;\n\n for (const step of pendingSteps) {\n assert(data, step.inputSchema ?? JsonObjectStruct);\n data = await step.migrate(data);\n assert(data, step.outputSchema ?? JsonObjectStruct);\n }\n\n return {\n version: latestVersion,\n state: {\n ...data,\n version: latestVersion,\n },\n migrated: pendingSteps.length > 0,\n } as MigrationResult<Data>;\n}\n\n/**\n * Build a {@link MigrationChain} wrapping the given internal steps.\n *\n * @param steps - The steps accumulated so far.\n * @returns A chain exposing `add`, `version`, and `apply`.\n */\nfunction buildChain<Data extends JsonObject>(\n steps: readonly MigrationStep[],\n): MigrationChain<Data> {\n return {\n version: steps.length,\n add: <Input extends Data, Output extends JsonObject>(\n step: MigrationStep<Input, Output>,\n ) => buildChain<Output>([...steps, step as unknown as MigrationStep]),\n apply: async (state) => applySteps<Data>(steps, state),\n };\n}\n\n/**\n * Start a new, empty migration chain.\n *\n * @example\n * ```typescript\n * const migrations = createMigrations()\n * .add({ migrate: (data) => ({ count: data.numberOfItems }) })\n * .add({ migrate: (data) => ({ ...data, createdAt: Date.now() }) }); // data typed as the first step's output, no cast\n *\n * const { state, migrated } = await migrations.apply(serializedState);\n * // Persist `state`; re-save if `migrated` is true.\n * ```\n * @returns An empty chain typed to `JsonObject`.\n */\nexport function createMigrations(): MigrationChain<JsonObject> {\n return buildChain<JsonObject>([]);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask/keyring-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "MetaMask Keyring SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"keyring",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@ethereumjs/tx": "^5.4.0",
|
|
70
|
-
"@metamask/eth-sig-util": "^
|
|
71
|
-
"@metamask/keyring-api": "^24.
|
|
70
|
+
"@metamask/eth-sig-util": "^9.0.0",
|
|
71
|
+
"@metamask/keyring-api": "^24.2.0",
|
|
72
72
|
"@metamask/keyring-utils": "^5.0.0",
|
|
73
73
|
"@metamask/scure-bip39": "^2.1.1",
|
|
74
74
|
"@metamask/superstruct": "^3.4.1",
|