@metamask-previews/account-tree-controller 8.1.0-preview-5bb6dba48 → 8.1.0-preview-d8fceeabc
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 +10 -0
- package/dist/state/snapshot.cjs +1 -1
- package/dist/state/snapshot.cjs.map +1 -1
- package/dist/state/snapshot.d.cts.map +1 -1
- package/dist/state/snapshot.d.mts.map +1 -1
- package/dist/state/snapshot.mjs +1 -1
- package/dist/state/snapshot.mjs.map +1 -1
- package/package.json +1 -1
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
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add `AccountTreeSnapshot:strip{Metadata,Secrets}` support ([#10112](https://github.com/MetaMask/core/pull/10112))
|
|
13
|
+
- This can be used to mutate the snapshot before calling `:importState` and ease custom flow integration where secrets and metadata would be imported in 2-steps.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **BREAKING:** `metadata` is now optional on `AccountWalletPayloadMetadata`, `AccountWalletGroupPayloadMetadata`, and all wallet/group payload entry types ([#10112](https://github.com/MetaMask/core/pull/10112))
|
|
18
|
+
- Consumers reading `metadata` from a snapshot or payload must now guard against `undefined` (e.g. after calling `AccountTreeSnapshot.stripMetadata()`).
|
|
19
|
+
|
|
10
20
|
## [8.1.0]
|
|
11
21
|
|
|
12
22
|
### Added
|
package/dist/state/snapshot.cjs
CHANGED
|
@@ -178,7 +178,7 @@ class AccountTreeSnapshot {
|
|
|
178
178
|
const { metadata: _groupMetadata, ...groupRest } = group;
|
|
179
179
|
return groupRest;
|
|
180
180
|
}),
|
|
181
|
-
};
|
|
181
|
+
}; // Looks like the compiler is not able to infer this correctly, but we just remove the `metadata` field out of any entry.
|
|
182
182
|
});
|
|
183
183
|
return new AccountTreeSnapshot(entries, __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f"));
|
|
184
184
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.cjs","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IdMap } from './id-map.js';\nimport type {\n AccountGroupPayloadId,\n AccountTreePayload,\n AccountTreeSnapshotGroup,\n AccountTreeSnapshotWallet,\n AccountTreeWalletEntry,\n AccountWalletMnemonicGroupEntry,\n AccountWalletMnemonicPayload,\n AccountWalletPayloadId,\n AccountWalletPrivateKeyGroupEntry,\n AccountWalletPrivateKeyPayload,\n} from './payload.js';\nimport {\n AccountWalletPayloadType,\n assertAccountTreePayload,\n ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n} from './payload.js';\nimport { deepFreeze } from './utils.js';\n\n/**\n * Immutable value object returned by {@link AccountTreeController.exportState}.\n *\n * Construct with {@link AccountTreeController.exportState},\n * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`\n * for tests and advanced use.\n *\n * Wallet and group entries are deep-cloned and deep-frozen once in the\n * constructor. Filtering predicates receive those read-only views directly;\n * each filter method returns a new snapshot that repeats the process for its\n * result.\n *\n * An optional ID map (local ↔ payload) may be supplied when bridging between\n * internal controller IDs and the stable cross-device IDs in the serialized\n * payload. The map covers the original export and is preserved unchanged\n * through filtering until {@link serialize}. Omit it when deterministic IDs\n * make {@link toLocalId} / {@link toPayloadId} unnecessary.\n */\nexport class AccountTreeSnapshot {\n readonly #entries: AccountTreeWalletEntry[];\n\n readonly #idMap: IdMap | undefined;\n\n /**\n * @param entries - Wallet entries in the snapshot.\n * @param idMap - Optional local ↔ payload ID map from export.\n */\n constructor(entries: AccountTreeWalletEntry[], idMap?: IdMap) {\n this.#entries = deepFreeze(structuredClone(entries));\n this.#idMap = idMap;\n }\n\n /**\n * Returns a new snapshot containing only the wallets for which\n * `predicate` returns `true`.\n *\n * When filtering by wallet ID, compare against stable payload IDs from\n * {@link serialize} or convert local IDs with {@link toPayloadId} first.\n *\n * @param predicate - Function called with each deeply read-only wallet entry.\n * @returns A filtered snapshot.\n */\n filterWallets(\n predicate: (wallet: AccountTreeSnapshotWallet) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries = this.#entries.filter((entry) =>\n predicate(entry as AccountTreeSnapshotWallet),\n );\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups within one wallet. Other wallets are left unchanged.\n *\n * Throws if `walletId` does not identify a wallet in the snapshot.\n * Removes the wallet if no groups remain after filtering - this prevents a\n * mnemonic wallet with zero selected groups from still transferring its secret.\n *\n * **Mnemonic wallets:** group indices must remain contiguous starting at 0\n * after filtering, because the payload schema enforces this invariant.\n * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will\n * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the\n * receiving end.\n *\n * @param walletId - Stable payload wallet ID to filter groups within.\n * @param predicate - Function called with each deeply read-only group entry.\n * @returns A filtered snapshot.\n * @throws If `walletId` is not present in the snapshot.\n */\n filterGroups(\n walletId: AccountWalletPayloadId,\n predicate: (group: AccountTreeSnapshotGroup) => boolean,\n ): AccountTreeSnapshot {\n const walletIndex = this.#entries.findIndex(\n (entry) => entry.id === walletId,\n );\n if (walletIndex === -1) {\n throw new Error(\n `Cannot filter groups: wallet \"${walletId}\" not found in snapshot`,\n );\n }\n\n const wallet = this.#entries[walletIndex];\n\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(group as AccountTreeSnapshotGroup),\n );\n\n const filteredEntries = [...this.#entries];\n if (filteredGroups.length === 0) {\n filteredEntries.splice(walletIndex, 1);\n } else if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n };\n } else {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n };\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups across every wallet.\n *\n * The parent wallet is provided as context to the predicate. Removes any\n * wallet with no remaining groups after filtering.\n *\n * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index\n * constraint that applies here as well.\n *\n * @param predicate - Function called with each group and its parent wallet.\n * @returns A filtered snapshot.\n */\n filterAllGroups(\n predicate: (\n group: AccountTreeSnapshotGroup,\n wallet: AccountTreeSnapshotWallet,\n ) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries: AccountTreeWalletEntry[] = [];\n\n for (const wallet of this.#entries) {\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(\n group as AccountTreeSnapshotGroup,\n wallet as AccountTreeSnapshotWallet,\n ),\n );\n\n if (filteredGroups.length === 0) {\n continue;\n }\n\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n });\n } else {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n });\n }\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all secret material removed - mnemonic\n * {@link AccountWalletMnemonicPayload.value | values} and private-key group\n * {@link AccountWalletPrivateKeyGroupEntry.value | values} are omitted.\n * Wallet and group metadata (names, pin, hidden) are preserved.\n *\n * Useful when only metadata (names, layout) needs to be applied and secrets\n * are already present in the vault.\n *\n * @returns A secrets-stripped snapshot.\n */\n stripSecrets(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n const { value: _value, ...rest } = wallet;\n return rest as AccountWalletMnemonicPayload;\n }\n return {\n ...wallet,\n groups: wallet.groups.map(\n ({ value: _value, ...group }): AccountWalletPrivateKeyGroupEntry =>\n group as AccountWalletPrivateKeyGroupEntry,\n ),\n } as AccountWalletPrivateKeyPayload;\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all metadata reset to defaults - wallet names\n * are cleared and group metadata (`name`, `pinned`, `hidden`) is reset.\n * Secret values are preserved.\n *\n * Useful when importing secrets into a vault in a separate step from applying\n * metadata - the metadata can be re-applied later from the original snapshot.\n *\n * @returns A metadata-stripped snapshot.\n */\n stripMetadata(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n const { metadata: _walletMetadata, ...walletRest } = wallet;\n return {\n ...walletRest,\n groups: wallet.groups.map((group) => {\n const { metadata: _groupMetadata, ...groupRest } = group;\n return groupRest as typeof group;\n }),\n } as AccountTreeWalletEntry;\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Converts a payload ID (wallet or group) to the corresponding local\n * `AccountTreeController` ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param payloadId - Stable cross-device wallet or group payload ID.\n * @returns The local controller ID, or `undefined` if not found or no ID map is present.\n */\n toLocalId(\n payloadId: AccountWalletPayloadId | AccountGroupPayloadId,\n ): ReturnType<IdMap['getLocalId']> {\n return this.#idMap?.getLocalId(payloadId);\n }\n\n /**\n * Converts a local `AccountTreeController` ID (wallet or group) to its\n * stable cross-device payload ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param localId - Local controller wallet or group ID.\n * @returns The payload ID, or `undefined` if not found or no ID map is present.\n */\n toPayloadId(\n localId: Parameters<IdMap['add']>[0],\n ): ReturnType<IdMap['getPayloadId']> {\n return this.#idMap?.getPayloadId(localId);\n }\n\n /**\n * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined\n * alongside the wallet entries.\n *\n * Returns the constructor-frozen wallet tree without copying it again.\n *\n * @returns The versioned flat payload.\n */\n serialize(): AccountTreePayload {\n return {\n version: ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n wallets: this.#entries,\n };\n }\n\n /**\n * Validates a raw value as an {@link AccountTreePayload}, running any\n * necessary version migrations, and returns an immutable snapshot.\n *\n * This is the entry point for untrusted serialized data. Unsupported schema\n * versions and wallet types fail closed with an error instead of returning a\n * partial snapshot.\n *\n * The returned snapshot has no ID map - {@link toLocalId} / {@link toPayloadId}\n * return `undefined`. Pass an {@link IdMap} to the constructor when you need\n * the map.\n *\n * @param raw - Unknown value to parse.\n * @returns A validated snapshot.\n * @throws If `raw` is not a valid payload or its version is unsupported.\n */\n static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> {\n // TODO: Use migration framework here.\n assertAccountTreePayload(raw);\n return new AccountTreeSnapshot(raw.wallets);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"snapshot.cjs","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IdMap } from './id-map.js';\nimport type {\n AccountGroupPayloadId,\n AccountTreePayload,\n AccountTreeSnapshotGroup,\n AccountTreeSnapshotWallet,\n AccountTreeWalletEntry,\n AccountWalletMnemonicGroupEntry,\n AccountWalletPayloadId,\n AccountWalletPrivateKeyGroupEntry,\n} from './payload.js';\nimport {\n AccountWalletPayloadType,\n assertAccountTreePayload,\n ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n} from './payload.js';\nimport { deepFreeze } from './utils.js';\n\n/**\n * Immutable value object returned by {@link AccountTreeController.exportState}.\n *\n * Construct with {@link AccountTreeController.exportState},\n * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`\n * for tests and advanced use.\n *\n * Wallet and group entries are deep-cloned and deep-frozen once in the\n * constructor. Filtering predicates receive those read-only views directly;\n * each filter method returns a new snapshot that repeats the process for its\n * result.\n *\n * An optional ID map (local ↔ payload) may be supplied when bridging between\n * internal controller IDs and the stable cross-device IDs in the serialized\n * payload. The map covers the original export and is preserved unchanged\n * through filtering until {@link serialize}. Omit it when deterministic IDs\n * make {@link toLocalId} / {@link toPayloadId} unnecessary.\n */\nexport class AccountTreeSnapshot {\n readonly #entries: AccountTreeWalletEntry[];\n\n readonly #idMap: IdMap | undefined;\n\n /**\n * @param entries - Wallet entries in the snapshot.\n * @param idMap - Optional local ↔ payload ID map from export.\n */\n constructor(entries: AccountTreeWalletEntry[], idMap?: IdMap) {\n this.#entries = deepFreeze(structuredClone(entries));\n this.#idMap = idMap;\n }\n\n /**\n * Returns a new snapshot containing only the wallets for which\n * `predicate` returns `true`.\n *\n * When filtering by wallet ID, compare against stable payload IDs from\n * {@link serialize} or convert local IDs with {@link toPayloadId} first.\n *\n * @param predicate - Function called with each deeply read-only wallet entry.\n * @returns A filtered snapshot.\n */\n filterWallets(\n predicate: (wallet: AccountTreeSnapshotWallet) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries = this.#entries.filter((entry) =>\n predicate(entry as AccountTreeSnapshotWallet),\n );\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups within one wallet. Other wallets are left unchanged.\n *\n * Throws if `walletId` does not identify a wallet in the snapshot.\n * Removes the wallet if no groups remain after filtering - this prevents a\n * mnemonic wallet with zero selected groups from still transferring its secret.\n *\n * **Mnemonic wallets:** group indices must remain contiguous starting at 0\n * after filtering, because the payload schema enforces this invariant.\n * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will\n * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the\n * receiving end.\n *\n * @param walletId - Stable payload wallet ID to filter groups within.\n * @param predicate - Function called with each deeply read-only group entry.\n * @returns A filtered snapshot.\n * @throws If `walletId` is not present in the snapshot.\n */\n filterGroups(\n walletId: AccountWalletPayloadId,\n predicate: (group: AccountTreeSnapshotGroup) => boolean,\n ): AccountTreeSnapshot {\n const walletIndex = this.#entries.findIndex(\n (entry) => entry.id === walletId,\n );\n if (walletIndex === -1) {\n throw new Error(\n `Cannot filter groups: wallet \"${walletId}\" not found in snapshot`,\n );\n }\n\n const wallet = this.#entries[walletIndex];\n\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(group as AccountTreeSnapshotGroup),\n );\n\n const filteredEntries = [...this.#entries];\n if (filteredGroups.length === 0) {\n filteredEntries.splice(walletIndex, 1);\n } else if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n };\n } else {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n };\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups across every wallet.\n *\n * The parent wallet is provided as context to the predicate. Removes any\n * wallet with no remaining groups after filtering.\n *\n * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index\n * constraint that applies here as well.\n *\n * @param predicate - Function called with each group and its parent wallet.\n * @returns A filtered snapshot.\n */\n filterAllGroups(\n predicate: (\n group: AccountTreeSnapshotGroup,\n wallet: AccountTreeSnapshotWallet,\n ) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries: AccountTreeWalletEntry[] = [];\n\n for (const wallet of this.#entries) {\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(\n group as AccountTreeSnapshotGroup,\n wallet as AccountTreeSnapshotWallet,\n ),\n );\n\n if (filteredGroups.length === 0) {\n continue;\n }\n\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n });\n } else {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n });\n }\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all secret material removed - mnemonic\n * {@link AccountWalletMnemonicPayload.value | values} and private-key group\n * {@link AccountWalletPrivateKeyGroupEntry.value | values} are omitted.\n * Wallet and group metadata (names, pin, hidden) are preserved.\n *\n * Useful when only metadata (names, layout) needs to be applied and secrets\n * are already present in the vault.\n *\n * @returns A secrets-stripped snapshot.\n */\n stripSecrets(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n const { value: _value, ...rest } = wallet;\n return rest;\n }\n return {\n ...wallet,\n groups: wallet.groups.map(\n ({ value: _value, ...group }): AccountWalletPrivateKeyGroupEntry =>\n group,\n ),\n };\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all metadata reset to defaults - wallet names\n * are cleared and group metadata (`name`, `pinned`, `hidden`) is reset.\n * Secret values are preserved.\n *\n * Useful when importing secrets into a vault in a separate step from applying\n * metadata - the metadata can be re-applied later from the original snapshot.\n *\n * @returns A metadata-stripped snapshot.\n */\n stripMetadata(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n const { metadata: _walletMetadata, ...walletRest } = wallet;\n return {\n ...walletRest,\n groups: wallet.groups.map((group) => {\n const { metadata: _groupMetadata, ...groupRest } = group;\n return groupRest as typeof group;\n }),\n } as AccountTreeWalletEntry; // Looks like the compiler is not able to infer this correctly, but we just remove the `metadata` field out of any entry.\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Converts a payload ID (wallet or group) to the corresponding local\n * `AccountTreeController` ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param payloadId - Stable cross-device wallet or group payload ID.\n * @returns The local controller ID, or `undefined` if not found or no ID map is present.\n */\n toLocalId(\n payloadId: AccountWalletPayloadId | AccountGroupPayloadId,\n ): ReturnType<IdMap['getLocalId']> {\n return this.#idMap?.getLocalId(payloadId);\n }\n\n /**\n * Converts a local `AccountTreeController` ID (wallet or group) to its\n * stable cross-device payload ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param localId - Local controller wallet or group ID.\n * @returns The payload ID, or `undefined` if not found or no ID map is present.\n */\n toPayloadId(\n localId: Parameters<IdMap['add']>[0],\n ): ReturnType<IdMap['getPayloadId']> {\n return this.#idMap?.getPayloadId(localId);\n }\n\n /**\n * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined\n * alongside the wallet entries.\n *\n * Returns the constructor-frozen wallet tree without copying it again.\n *\n * @returns The versioned flat payload.\n */\n serialize(): AccountTreePayload {\n return {\n version: ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n wallets: this.#entries,\n };\n }\n\n /**\n * Validates a raw value as an {@link AccountTreePayload}, running any\n * necessary version migrations, and returns an immutable snapshot.\n *\n * This is the entry point for untrusted serialized data. Unsupported schema\n * versions and wallet types fail closed with an error instead of returning a\n * partial snapshot.\n *\n * The returned snapshot has no ID map - {@link toLocalId} / {@link toPayloadId}\n * return `undefined`. Pass an {@link IdMap} to the constructor when you need\n * the map.\n *\n * @param raw - Unknown value to parse.\n * @returns A validated snapshot.\n * @throws If `raw` is not a valid payload or its version is unsupported.\n */\n static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> {\n // TODO: Use migration framework here.\n assertAccountTreePayload(raw);\n return new AccountTreeSnapshot(raw.wallets);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.cts","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAoB;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"snapshot.d.cts","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAoB;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EAEtB,sBAAsB,EAEvB,sBAAqB;AAQtB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,mBAAmB;;IAK9B;;;OAGG;gBACS,OAAO,EAAE,sBAAsB,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK;IAK5D;;;;;;;;;OASG;IACH,aAAa,CACX,SAAS,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,OAAO,GACxD,mBAAmB;IAQtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CACV,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,OAAO,GACtD,mBAAmB;IAkCtB;;;;;;;;;;;OAWG;IACH,eAAe,CACb,SAAS,EAAE,CACT,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,yBAAyB,KAC9B,OAAO,GACX,mBAAmB;IA+BtB;;;;;;;;;;OAUG;IACH,YAAY,IAAI,mBAAmB;IAiBnC;;;;;;;;;OASG;IACH,aAAa,IAAI,mBAAmB;IAcpC;;;;;;;;;OASG;IACH,SAAS,CACP,SAAS,EAAE,sBAAsB,GAAG,qBAAqB,GACxD,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAIlC;;;;;;;;;OASG;IACH,WAAW,CACT,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GACnC,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAIpC;;;;;;;OAOG;IACH,SAAS,IAAI,kBAAkB;IAO/B;;;;;;;;;;;;;;;OAeG;WACU,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAKrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.mts","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAoB;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"snapshot.d.mts","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAoB;AACzC,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EAEtB,sBAAsB,EAEvB,sBAAqB;AAQtB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,mBAAmB;;IAK9B;;;OAGG;gBACS,OAAO,EAAE,sBAAsB,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK;IAK5D;;;;;;;;;OASG;IACH,aAAa,CACX,SAAS,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,OAAO,GACxD,mBAAmB;IAQtB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CACV,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,OAAO,GACtD,mBAAmB;IAkCtB;;;;;;;;;;;OAWG;IACH,eAAe,CACb,SAAS,EAAE,CACT,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,yBAAyB,KAC9B,OAAO,GACX,mBAAmB;IA+BtB;;;;;;;;;;OAUG;IACH,YAAY,IAAI,mBAAmB;IAiBnC;;;;;;;;;OASG;IACH,aAAa,IAAI,mBAAmB;IAcpC;;;;;;;;;OASG;IACH,SAAS,CACP,SAAS,EAAE,sBAAsB,GAAG,qBAAqB,GACxD,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAIlC;;;;;;;;;OASG;IACH,WAAW,CACT,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GACnC,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAIpC;;;;;;;OAOG;IACH,SAAS,IAAI,kBAAkB;IAO/B;;;;;;;;;;;;;;;OAeG;WACU,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAKrE"}
|
package/dist/state/snapshot.mjs
CHANGED
|
@@ -175,7 +175,7 @@ export class AccountTreeSnapshot {
|
|
|
175
175
|
const { metadata: _groupMetadata, ...groupRest } = group;
|
|
176
176
|
return groupRest;
|
|
177
177
|
}),
|
|
178
|
-
};
|
|
178
|
+
}; // Looks like the compiler is not able to infer this correctly, but we just remove the `metadata` field out of any entry.
|
|
179
179
|
});
|
|
180
180
|
return new AccountTreeSnapshot(entries, __classPrivateFieldGet(this, _AccountTreeSnapshot_idMap, "f"));
|
|
181
181
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.mjs","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IdMap } from './id-map.js';\nimport type {\n AccountGroupPayloadId,\n AccountTreePayload,\n AccountTreeSnapshotGroup,\n AccountTreeSnapshotWallet,\n AccountTreeWalletEntry,\n AccountWalletMnemonicGroupEntry,\n AccountWalletMnemonicPayload,\n AccountWalletPayloadId,\n AccountWalletPrivateKeyGroupEntry,\n AccountWalletPrivateKeyPayload,\n} from './payload.js';\nimport {\n AccountWalletPayloadType,\n assertAccountTreePayload,\n ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n} from './payload.js';\nimport { deepFreeze } from './utils.js';\n\n/**\n * Immutable value object returned by {@link AccountTreeController.exportState}.\n *\n * Construct with {@link AccountTreeController.exportState},\n * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`\n * for tests and advanced use.\n *\n * Wallet and group entries are deep-cloned and deep-frozen once in the\n * constructor. Filtering predicates receive those read-only views directly;\n * each filter method returns a new snapshot that repeats the process for its\n * result.\n *\n * An optional ID map (local ↔ payload) may be supplied when bridging between\n * internal controller IDs and the stable cross-device IDs in the serialized\n * payload. The map covers the original export and is preserved unchanged\n * through filtering until {@link serialize}. Omit it when deterministic IDs\n * make {@link toLocalId} / {@link toPayloadId} unnecessary.\n */\nexport class AccountTreeSnapshot {\n readonly #entries: AccountTreeWalletEntry[];\n\n readonly #idMap: IdMap | undefined;\n\n /**\n * @param entries - Wallet entries in the snapshot.\n * @param idMap - Optional local ↔ payload ID map from export.\n */\n constructor(entries: AccountTreeWalletEntry[], idMap?: IdMap) {\n this.#entries = deepFreeze(structuredClone(entries));\n this.#idMap = idMap;\n }\n\n /**\n * Returns a new snapshot containing only the wallets for which\n * `predicate` returns `true`.\n *\n * When filtering by wallet ID, compare against stable payload IDs from\n * {@link serialize} or convert local IDs with {@link toPayloadId} first.\n *\n * @param predicate - Function called with each deeply read-only wallet entry.\n * @returns A filtered snapshot.\n */\n filterWallets(\n predicate: (wallet: AccountTreeSnapshotWallet) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries = this.#entries.filter((entry) =>\n predicate(entry as AccountTreeSnapshotWallet),\n );\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups within one wallet. Other wallets are left unchanged.\n *\n * Throws if `walletId` does not identify a wallet in the snapshot.\n * Removes the wallet if no groups remain after filtering - this prevents a\n * mnemonic wallet with zero selected groups from still transferring its secret.\n *\n * **Mnemonic wallets:** group indices must remain contiguous starting at 0\n * after filtering, because the payload schema enforces this invariant.\n * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will\n * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the\n * receiving end.\n *\n * @param walletId - Stable payload wallet ID to filter groups within.\n * @param predicate - Function called with each deeply read-only group entry.\n * @returns A filtered snapshot.\n * @throws If `walletId` is not present in the snapshot.\n */\n filterGroups(\n walletId: AccountWalletPayloadId,\n predicate: (group: AccountTreeSnapshotGroup) => boolean,\n ): AccountTreeSnapshot {\n const walletIndex = this.#entries.findIndex(\n (entry) => entry.id === walletId,\n );\n if (walletIndex === -1) {\n throw new Error(\n `Cannot filter groups: wallet \"${walletId}\" not found in snapshot`,\n );\n }\n\n const wallet = this.#entries[walletIndex];\n\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(group as AccountTreeSnapshotGroup),\n );\n\n const filteredEntries = [...this.#entries];\n if (filteredGroups.length === 0) {\n filteredEntries.splice(walletIndex, 1);\n } else if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n };\n } else {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n };\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups across every wallet.\n *\n * The parent wallet is provided as context to the predicate. Removes any\n * wallet with no remaining groups after filtering.\n *\n * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index\n * constraint that applies here as well.\n *\n * @param predicate - Function called with each group and its parent wallet.\n * @returns A filtered snapshot.\n */\n filterAllGroups(\n predicate: (\n group: AccountTreeSnapshotGroup,\n wallet: AccountTreeSnapshotWallet,\n ) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries: AccountTreeWalletEntry[] = [];\n\n for (const wallet of this.#entries) {\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(\n group as AccountTreeSnapshotGroup,\n wallet as AccountTreeSnapshotWallet,\n ),\n );\n\n if (filteredGroups.length === 0) {\n continue;\n }\n\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n });\n } else {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n });\n }\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all secret material removed - mnemonic\n * {@link AccountWalletMnemonicPayload.value | values} and private-key group\n * {@link AccountWalletPrivateKeyGroupEntry.value | values} are omitted.\n * Wallet and group metadata (names, pin, hidden) are preserved.\n *\n * Useful when only metadata (names, layout) needs to be applied and secrets\n * are already present in the vault.\n *\n * @returns A secrets-stripped snapshot.\n */\n stripSecrets(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n const { value: _value, ...rest } = wallet;\n return rest as AccountWalletMnemonicPayload;\n }\n return {\n ...wallet,\n groups: wallet.groups.map(\n ({ value: _value, ...group }): AccountWalletPrivateKeyGroupEntry =>\n group as AccountWalletPrivateKeyGroupEntry,\n ),\n } as AccountWalletPrivateKeyPayload;\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all metadata reset to defaults - wallet names\n * are cleared and group metadata (`name`, `pinned`, `hidden`) is reset.\n * Secret values are preserved.\n *\n * Useful when importing secrets into a vault in a separate step from applying\n * metadata - the metadata can be re-applied later from the original snapshot.\n *\n * @returns A metadata-stripped snapshot.\n */\n stripMetadata(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n const { metadata: _walletMetadata, ...walletRest } = wallet;\n return {\n ...walletRest,\n groups: wallet.groups.map((group) => {\n const { metadata: _groupMetadata, ...groupRest } = group;\n return groupRest as typeof group;\n }),\n } as AccountTreeWalletEntry;\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Converts a payload ID (wallet or group) to the corresponding local\n * `AccountTreeController` ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param payloadId - Stable cross-device wallet or group payload ID.\n * @returns The local controller ID, or `undefined` if not found or no ID map is present.\n */\n toLocalId(\n payloadId: AccountWalletPayloadId | AccountGroupPayloadId,\n ): ReturnType<IdMap['getLocalId']> {\n return this.#idMap?.getLocalId(payloadId);\n }\n\n /**\n * Converts a local `AccountTreeController` ID (wallet or group) to its\n * stable cross-device payload ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param localId - Local controller wallet or group ID.\n * @returns The payload ID, or `undefined` if not found or no ID map is present.\n */\n toPayloadId(\n localId: Parameters<IdMap['add']>[0],\n ): ReturnType<IdMap['getPayloadId']> {\n return this.#idMap?.getPayloadId(localId);\n }\n\n /**\n * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined\n * alongside the wallet entries.\n *\n * Returns the constructor-frozen wallet tree without copying it again.\n *\n * @returns The versioned flat payload.\n */\n serialize(): AccountTreePayload {\n return {\n version: ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n wallets: this.#entries,\n };\n }\n\n /**\n * Validates a raw value as an {@link AccountTreePayload}, running any\n * necessary version migrations, and returns an immutable snapshot.\n *\n * This is the entry point for untrusted serialized data. Unsupported schema\n * versions and wallet types fail closed with an error instead of returning a\n * partial snapshot.\n *\n * The returned snapshot has no ID map - {@link toLocalId} / {@link toPayloadId}\n * return `undefined`. Pass an {@link IdMap} to the constructor when you need\n * the map.\n *\n * @param raw - Unknown value to parse.\n * @returns A validated snapshot.\n * @throws If `raw` is not a valid payload or its version is unsupported.\n */\n static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> {\n // TODO: Use migration framework here.\n assertAccountTreePayload(raw);\n return new AccountTreeSnapshot(raw.wallets);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"snapshot.mjs","sourceRoot":"","sources":["../../src/state/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IdMap } from './id-map.js';\nimport type {\n AccountGroupPayloadId,\n AccountTreePayload,\n AccountTreeSnapshotGroup,\n AccountTreeSnapshotWallet,\n AccountTreeWalletEntry,\n AccountWalletMnemonicGroupEntry,\n AccountWalletPayloadId,\n AccountWalletPrivateKeyGroupEntry,\n} from './payload.js';\nimport {\n AccountWalletPayloadType,\n assertAccountTreePayload,\n ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n} from './payload.js';\nimport { deepFreeze } from './utils.js';\n\n/**\n * Immutable value object returned by {@link AccountTreeController.exportState}.\n *\n * Construct with {@link AccountTreeController.exportState},\n * {@link AccountTreeSnapshot.deserialize}, or `new AccountTreeSnapshot(...)`\n * for tests and advanced use.\n *\n * Wallet and group entries are deep-cloned and deep-frozen once in the\n * constructor. Filtering predicates receive those read-only views directly;\n * each filter method returns a new snapshot that repeats the process for its\n * result.\n *\n * An optional ID map (local ↔ payload) may be supplied when bridging between\n * internal controller IDs and the stable cross-device IDs in the serialized\n * payload. The map covers the original export and is preserved unchanged\n * through filtering until {@link serialize}. Omit it when deterministic IDs\n * make {@link toLocalId} / {@link toPayloadId} unnecessary.\n */\nexport class AccountTreeSnapshot {\n readonly #entries: AccountTreeWalletEntry[];\n\n readonly #idMap: IdMap | undefined;\n\n /**\n * @param entries - Wallet entries in the snapshot.\n * @param idMap - Optional local ↔ payload ID map from export.\n */\n constructor(entries: AccountTreeWalletEntry[], idMap?: IdMap) {\n this.#entries = deepFreeze(structuredClone(entries));\n this.#idMap = idMap;\n }\n\n /**\n * Returns a new snapshot containing only the wallets for which\n * `predicate` returns `true`.\n *\n * When filtering by wallet ID, compare against stable payload IDs from\n * {@link serialize} or convert local IDs with {@link toPayloadId} first.\n *\n * @param predicate - Function called with each deeply read-only wallet entry.\n * @returns A filtered snapshot.\n */\n filterWallets(\n predicate: (wallet: AccountTreeSnapshotWallet) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries = this.#entries.filter((entry) =>\n predicate(entry as AccountTreeSnapshotWallet),\n );\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups within one wallet. Other wallets are left unchanged.\n *\n * Throws if `walletId` does not identify a wallet in the snapshot.\n * Removes the wallet if no groups remain after filtering - this prevents a\n * mnemonic wallet with zero selected groups from still transferring its secret.\n *\n * **Mnemonic wallets:** group indices must remain contiguous starting at 0\n * after filtering, because the payload schema enforces this invariant.\n * Predicates that produce gaps (e.g. keeping only index 1, or 0 and 2) will\n * cause {@link AccountTreeSnapshot.deserialize} to reject the payload on the\n * receiving end.\n *\n * @param walletId - Stable payload wallet ID to filter groups within.\n * @param predicate - Function called with each deeply read-only group entry.\n * @returns A filtered snapshot.\n * @throws If `walletId` is not present in the snapshot.\n */\n filterGroups(\n walletId: AccountWalletPayloadId,\n predicate: (group: AccountTreeSnapshotGroup) => boolean,\n ): AccountTreeSnapshot {\n const walletIndex = this.#entries.findIndex(\n (entry) => entry.id === walletId,\n );\n if (walletIndex === -1) {\n throw new Error(\n `Cannot filter groups: wallet \"${walletId}\" not found in snapshot`,\n );\n }\n\n const wallet = this.#entries[walletIndex];\n\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(group as AccountTreeSnapshotGroup),\n );\n\n const filteredEntries = [...this.#entries];\n if (filteredGroups.length === 0) {\n filteredEntries.splice(walletIndex, 1);\n } else if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n };\n } else {\n filteredEntries[walletIndex] = {\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n };\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Filters groups across every wallet.\n *\n * The parent wallet is provided as context to the predicate. Removes any\n * wallet with no remaining groups after filtering.\n *\n * **Mnemonic wallets:** see {@link filterGroups} for the contiguous-index\n * constraint that applies here as well.\n *\n * @param predicate - Function called with each group and its parent wallet.\n * @returns A filtered snapshot.\n */\n filterAllGroups(\n predicate: (\n group: AccountTreeSnapshotGroup,\n wallet: AccountTreeSnapshotWallet,\n ) => boolean,\n ): AccountTreeSnapshot {\n const filteredEntries: AccountTreeWalletEntry[] = [];\n\n for (const wallet of this.#entries) {\n const filteredGroups = wallet.groups.filter((group) =>\n predicate(\n group as AccountTreeSnapshotGroup,\n wallet as AccountTreeSnapshotWallet,\n ),\n );\n\n if (filteredGroups.length === 0) {\n continue;\n }\n\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletMnemonicGroupEntry[],\n });\n } else {\n filteredEntries.push({\n ...wallet,\n groups: filteredGroups as AccountWalletPrivateKeyGroupEntry[],\n });\n }\n }\n\n return new AccountTreeSnapshot(filteredEntries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all secret material removed - mnemonic\n * {@link AccountWalletMnemonicPayload.value | values} and private-key group\n * {@link AccountWalletPrivateKeyGroupEntry.value | values} are omitted.\n * Wallet and group metadata (names, pin, hidden) are preserved.\n *\n * Useful when only metadata (names, layout) needs to be applied and secrets\n * are already present in the vault.\n *\n * @returns A secrets-stripped snapshot.\n */\n stripSecrets(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n if (wallet.type === AccountWalletPayloadType.Mnemonic) {\n const { value: _value, ...rest } = wallet;\n return rest;\n }\n return {\n ...wallet,\n groups: wallet.groups.map(\n ({ value: _value, ...group }): AccountWalletPrivateKeyGroupEntry =>\n group,\n ),\n };\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Returns a new snapshot with all metadata reset to defaults - wallet names\n * are cleared and group metadata (`name`, `pinned`, `hidden`) is reset.\n * Secret values are preserved.\n *\n * Useful when importing secrets into a vault in a separate step from applying\n * metadata - the metadata can be re-applied later from the original snapshot.\n *\n * @returns A metadata-stripped snapshot.\n */\n stripMetadata(): AccountTreeSnapshot {\n const entries = this.#entries.map((wallet): AccountTreeWalletEntry => {\n const { metadata: _walletMetadata, ...walletRest } = wallet;\n return {\n ...walletRest,\n groups: wallet.groups.map((group) => {\n const { metadata: _groupMetadata, ...groupRest } = group;\n return groupRest as typeof group;\n }),\n } as AccountTreeWalletEntry; // Looks like the compiler is not able to infer this correctly, but we just remove the `metadata` field out of any entry.\n });\n return new AccountTreeSnapshot(entries, this.#idMap);\n }\n\n /**\n * Converts a payload ID (wallet or group) to the corresponding local\n * `AccountTreeController` ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param payloadId - Stable cross-device wallet or group payload ID.\n * @returns The local controller ID, or `undefined` if not found or no ID map is present.\n */\n toLocalId(\n payloadId: AccountWalletPayloadId | AccountGroupPayloadId,\n ): ReturnType<IdMap['getLocalId']> {\n return this.#idMap?.getLocalId(payloadId);\n }\n\n /**\n * Converts a local `AccountTreeController` ID (wallet or group) to its\n * stable cross-device payload ID.\n *\n * The map reflects the original export, not the wallets/groups currently\n * retained in this snapshot after filtering.\n *\n * @param localId - Local controller wallet or group ID.\n * @returns The payload ID, or `undefined` if not found or no ID map is present.\n */\n toPayloadId(\n localId: Parameters<IdMap['add']>[0],\n ): ReturnType<IdMap['getPayloadId']> {\n return this.#idMap?.getPayloadId(localId);\n }\n\n /**\n * Serializes the snapshot to a flat {@link AccountTreePayload} with `version` inlined\n * alongside the wallet entries.\n *\n * Returns the constructor-frozen wallet tree without copying it again.\n *\n * @returns The versioned flat payload.\n */\n serialize(): AccountTreePayload {\n return {\n version: ACCOUNT_TREE_PAYLOAD_CURRENT_VERSION,\n wallets: this.#entries,\n };\n }\n\n /**\n * Validates a raw value as an {@link AccountTreePayload}, running any\n * necessary version migrations, and returns an immutable snapshot.\n *\n * This is the entry point for untrusted serialized data. Unsupported schema\n * versions and wallet types fail closed with an error instead of returning a\n * partial snapshot.\n *\n * The returned snapshot has no ID map - {@link toLocalId} / {@link toPayloadId}\n * return `undefined`. Pass an {@link IdMap} to the constructor when you need\n * the map.\n *\n * @param raw - Unknown value to parse.\n * @returns A validated snapshot.\n * @throws If `raw` is not a valid payload or its version is unsupported.\n */\n static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> {\n // TODO: Use migration framework here.\n assertAccountTreePayload(raw);\n return new AccountTreeSnapshot(raw.wallets);\n }\n}\n"]}
|
package/package.json
CHANGED