Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations. pdsmoover.com
pds atproto migrations moo cow

moved to the oxfmt

authored by baileytownsend.dev and committed by tangled.org d829485d b6b47d85

+5499 -1761
+8
.oxfmtrc.json
···
··· 1 + { 2 + "$schema": "https://unpkg.com/oxfmt/configuration_schema.json", 3 + "semi": false, 4 + "singleQuote": true, 5 + "arrowParens": "avoid", 6 + "quoteProps": "consistent", 7 + "experimentalSortPackageJson": false 8 + }
+113
.oxlintrc.json
···
··· 1 + { 2 + "$schema": "https://unpkg.com/oxlint/configuration_schema.json", 3 + "plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"], 4 + "jsPlugins": ["@e18e/eslint-plugin", "eslint-plugin-regexp"], 5 + "categories": { 6 + "correctness": "error", 7 + "suspicious": "warn", 8 + "perf": "warn" 9 + }, 10 + "rules": { 11 + "no-console": "warn", 12 + "no-await-in-loop": "off", 13 + "unicorn/no-array-sort": "off", 14 + "no-restricted-globals": "error", 15 + "typescript/consistent-type-imports": "error", 16 + "e18e/prefer-array-from-map": "error", 17 + "e18e/prefer-timer-args": "error", 18 + "e18e/prefer-date-now": "error", 19 + "e18e/prefer-regex-test": "error", 20 + "e18e/prefer-array-some": "error", 21 + // RegExp - Possible Errors (most critical) 22 + "regexp/no-contradiction-with-assertion": "error", 23 + "regexp/no-dupe-disjunctions": "error", 24 + "regexp/no-empty-alternative": "error", 25 + "regexp/no-empty-capturing-group": "error", 26 + "regexp/no-empty-character-class": "error", 27 + "regexp/no-empty-group": "error", 28 + "regexp/no-empty-lookarounds-assertion": "error", 29 + "regexp/no-escape-backspace": "error", 30 + "regexp/no-invalid-regexp": "error", 31 + "regexp/no-lazy-ends": "error", 32 + "regexp/no-misleading-capturing-group": "error", 33 + "regexp/no-misleading-unicode-character": "error", 34 + "regexp/no-missing-g-flag": "error", 35 + "regexp/no-optional-assertion": "error", 36 + "regexp/no-potentially-useless-backreference": "error", 37 + "regexp/no-super-linear-backtracking": "error", 38 + "regexp/no-useless-assertions": "error", 39 + "regexp/no-useless-backreference": "error", 40 + "regexp/no-useless-dollar-replacements": "error", 41 + "regexp/strict": "error", 42 + // RegExp - Best Practices 43 + "regexp/confusing-quantifier": "warn", 44 + "regexp/control-character-escape": "error", 45 + "regexp/negation": "error", 46 + "regexp/no-dupe-characters-character-class": "error", 47 + "regexp/no-empty-string-literal": "error", 48 + "regexp/no-extra-lookaround-assertions": "error", 49 + "regexp/no-invisible-character": "error", 50 + "regexp/no-legacy-features": "error", 51 + "regexp/no-non-standard-flag": "error", 52 + "regexp/no-obscure-range": "error", 53 + "regexp/no-octal": "error", 54 + "regexp/no-standalone-backslash": "error", 55 + "regexp/no-trivially-nested-assertion": "error", 56 + "regexp/no-trivially-nested-quantifier": "error", 57 + "regexp/no-unused-capturing-group": "warn", 58 + "regexp/no-useless-character-class": "error", 59 + "regexp/no-useless-flag": "error", 60 + "regexp/no-useless-lazy": "error", 61 + "regexp/no-useless-quantifier": "error", 62 + "regexp/no-useless-range": "error", 63 + "regexp/no-useless-set-operand": "error", 64 + "regexp/no-useless-string-literal": "error", 65 + "regexp/no-useless-two-nums-quantifier": "error", 66 + "regexp/no-zero-quantifier": "error", 67 + "regexp/optimal-lookaround-quantifier": "warn", 68 + "regexp/optimal-quantifier-concatenation": "error", 69 + "regexp/prefer-predefined-assertion": "error", 70 + "regexp/prefer-range": "error", 71 + "regexp/prefer-set-operation": "error", 72 + "regexp/simplify-set-operations": "error", 73 + "regexp/use-ignore-case": "error", 74 + // RegExp - Stylistic Issues (less critical, focused on consistency) 75 + "regexp/match-any": "warn", 76 + "regexp/no-useless-escape": "warn", 77 + "regexp/no-useless-non-capturing-group": "warn", 78 + "regexp/prefer-character-class": "warn", 79 + "regexp/prefer-d": "warn", 80 + "regexp/prefer-plus-quantifier": "warn", 81 + "regexp/prefer-question-quantifier": "warn", 82 + "regexp/prefer-star-quantifier": "warn", 83 + "regexp/prefer-unicode-codepoint-escapes": "warn", 84 + "regexp/prefer-w": "warn", 85 + "regexp/sort-flags": "warn" 86 + }, 87 + "overrides": [ 88 + { 89 + "files": [ 90 + "server/**/*", 91 + "cli/**/*", 92 + "scripts/**/*", 93 + "modules/**/*", 94 + "app/components/OgImage/*" 95 + ], 96 + "rules": { 97 + "no-console": "off" 98 + } 99 + } 100 + ], 101 + "ignorePatterns": [ 102 + ".output/**", 103 + ".data/**", 104 + ".nuxt/**", 105 + ".nitro/**", 106 + ".cache/**", 107 + "dist/**", 108 + "node_modules/**", 109 + "coverage/**", 110 + "playwright-report/**", 111 + "test-results/**" 112 + ] 113 + }
+87
.zed/settings.json
···
··· 1 + { 2 + "lsp": { 3 + "oxlint": { 4 + "initialization_options": { 5 + "settings": { 6 + "configPath": ".oxlintrc.json", 7 + "run": "onType" 8 + } 9 + } 10 + }, 11 + "oxfmt": { 12 + "initialization_options": { 13 + "settings": { 14 + "run": "onSave", 15 + "configPath": ".oxfmtrc.json" 16 + } 17 + } 18 + } 19 + }, 20 + "languages": { 21 + "TypeScript": { 22 + "format_on_save": "on", 23 + "prettier": { 24 + "allowed": false 25 + }, 26 + "formatter": [ 27 + { 28 + "language_server": { 29 + "name": "oxfmt" 30 + } 31 + } 32 + ] 33 + }, 34 + "Svelte": { 35 + "format_on_save": "on", 36 + "prettier": { 37 + "allowed": false 38 + }, 39 + "formatter": [ 40 + { 41 + "language_server": { 42 + "name": "oxfmt" 43 + } 44 + } 45 + ] 46 + }, 47 + "JSON": { 48 + "format_on_save": "on", 49 + "prettier": { 50 + "allowed": false 51 + }, 52 + "formatter": [ 53 + { 54 + "language_server": { 55 + "name": "oxfmt" 56 + } 57 + } 58 + ] 59 + }, 60 + "JSONC": { 61 + "format_on_save": "on", 62 + "prettier": { 63 + "allowed": false 64 + }, 65 + "formatter": [ 66 + { 67 + "language_server": { 68 + "name": "oxfmt" 69 + } 70 + } 71 + ] 72 + }, 73 + "YAML": { 74 + "format_on_save": "on", 75 + "prettier": { 76 + "allowed": false 77 + }, 78 + "formatter": [ 79 + { 80 + "language_server": { 81 + "name": "oxfmt" 82 + } 83 + } 84 + ] 85 + } 86 + } 87 + }
+1 -1
packages/moover/README.md
··· 12 instance 13 - [MissingBlobs](./lib/missingBlobs.js) - Finds missing blobs on your old PDS and uploads them to your new PDS 14 - [PlcOps](./lib/plc-ops.js) - Helpers for manual PCL operations 15 - - [Restore](./lib/restore.js) - Handles a recovery and restores the at proto from the backup
··· 12 instance 13 - [MissingBlobs](./lib/missingBlobs.js) - Finds missing blobs on your old PDS and uploads them to your new PDS 14 - [PlcOps](./lib/plc-ops.js) - Helpers for manual PCL operations 15 + - [Restore](./lib/restore.js) - Handles a recovery and restores the at proto from the backup
+9 -9
packages/moover/index.html
··· 1 <!doctype html> 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"/> 5 - <link rel="icon" type="image/svg+xml" href="/vite.svg"/> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0"/> 7 <title>moover</title> 8 - </head> 9 - <body> 10 - <div id="app"></div> 11 - <script type="module" src="/lib/main.js"></script> 12 - </body> 13 </html>
··· 1 <!doctype html> 2 <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 <title>moover</title> 8 + </head> 9 + <body> 10 + <div id="app"></div> 11 + <script type="module" src="/lib/main.js"></script> 12 + </body> 13 </html>
+50
packages/moover/lexicons/blue/microcosm/identity/resolveMiniDoc.json
···
··· 1 + { 2 + "id": "blue.microcosm.identity.resolveMiniDoc", 3 + "defs": { 4 + "main": { 5 + "type": "query", 6 + "output": { 7 + "schema": { 8 + "type": "object", 9 + "required": ["did", "handle", "pds", "signing_key"], 10 + "properties": { 11 + "did": { 12 + "type": "string", 13 + "format": "did", 14 + "description": "DID, bi-directionally verified if a handle was provided in the query." 15 + }, 16 + "pds": { 17 + "type": "string", 18 + "format": "uri", 19 + "description": "The identity's PDS URL" 20 + }, 21 + "handle": { 22 + "type": "string", 23 + "format": "handle", 24 + "description": "The validated handle of the account or `handle.invalid` if the handle\ndid not bi-directionally match the DID document." 25 + }, 26 + "signing_key": { 27 + "type": "string", 28 + "description": "The atproto signing key publicKeyMultibase\n\nLegacy key encoding not supported. the key is returned directly; `id`,\n`type`, and `controller` are omitted." 29 + } 30 + } 31 + }, 32 + "encoding": "application/json" 33 + }, 34 + "parameters": { 35 + "type": "params", 36 + "required": ["identifier"], 37 + "properties": { 38 + "identifier": { 39 + "type": "string", 40 + "format": "at-identifier", 41 + "description": "Handle or DID to resolve" 42 + } 43 + } 44 + }, 45 + "description": "Like [com.atproto.identity.resolveIdentity](https://docs.bsky.app/docs/api/com-atproto-identity-resolve-identity) but instead of the full `didDoc` it returns an atproto-relevant subset." 46 + } 47 + }, 48 + "$type": "com.atproto.lexicon.schema", 49 + "lexicon": 1 50 + }
+47 -50
packages/moover/lib/atprotoUtils.js
··· 1 import { 2 - CompositeDidDocumentResolver, CompositeHandleResolver, 3 - DohJsonHandleResolver, 4 - PlcDidDocumentResolver, WebDidDocumentResolver, 5 - WellKnownHandleResolver 6 - } from '@atcute/identity-resolver'; 7 8 const handleResolver = new CompositeHandleResolver({ 9 - strategy: 'race', 10 - methods: { 11 - dns: new DohJsonHandleResolver({ 12 - dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query', 13 - }), 14 - http: new WellKnownHandleResolver(), 15 - }, 16 - }); 17 18 const docResolver = new CompositeDidDocumentResolver({ 19 - methods: { 20 - plc: new PlcDidDocumentResolver(), 21 - web: new WebDidDocumentResolver(), 22 - }, 23 - }); 24 25 /** 26 * Cleans the handle of @ and some other unicode characters that used to show up when copied from the profile 27 * @param handle {string} 28 * @returns {string} 29 */ 30 - const cleanHandle = (handle) => 31 - handle.replace('@', '').trim().replace( 32 - /[\u202A\u202C\u200E\u200F\u2066-\u2069]/g, 33 - '', 34 - ); 35 - 36 37 /** 38 * Convince helper to resolve a handle to a did and then find the PDS url from the did document. ··· 41 * @returns {Promise<{usersDid: string, pds: string}>} 42 */ 43 async function handleAndPDSResolver(handle) { 44 - let usersDid = null; 45 - if (handle.startsWith('did:')) { 46 - usersDid = handle; 47 - } else { 48 - const cleanedHandle = cleanHandle(handle); 49 - usersDid = await handleResolver.resolve(cleanedHandle); 50 - } 51 - const didDoc = await docResolver.resolve(usersDid); 52 53 - let pds; 54 - try { 55 - pds = didDoc.service?.filter((s) => 56 - s.type === 'AtprotoPersonalDataServer' 57 - )[0].serviceEndpoint; 58 - } catch (error) { 59 - throw new Error('Could not find a PDS in the DID document.'); 60 - } 61 - return {usersDid, pds}; 62 } 63 - 64 65 /** 66 * Fetches the DID Web from the .well-known/did.json endpoint of the server. ··· 69 * @returns {Promise<*>} 70 */ 71 async function fetchPDSMooverDIDWeb(baseUrl) { 72 - const response = await fetch(`${baseUrl}/.well-known/did.json`); 73 - if (!response.ok) { 74 - throw new Error(`Failed to fetch DID document: ${response.status}`); 75 - } 76 - const didDoc = await response.json(); 77 - return didDoc.id; 78 } 79 80 - 81 - export {handleResolver, docResolver, cleanHandle, handleAndPDSResolver, fetchPDSMooverDIDWeb};
··· 1 import { 2 + CompositeDidDocumentResolver, 3 + CompositeHandleResolver, 4 + DohJsonHandleResolver, 5 + PlcDidDocumentResolver, 6 + WebDidDocumentResolver, 7 + WellKnownHandleResolver, 8 + } from '@atcute/identity-resolver' 9 10 const handleResolver = new CompositeHandleResolver({ 11 + strategy: 'race', 12 + methods: { 13 + dns: new DohJsonHandleResolver({ 14 + dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query', 15 + }), 16 + http: new WellKnownHandleResolver(), 17 + }, 18 + }) 19 20 const docResolver = new CompositeDidDocumentResolver({ 21 + methods: { 22 + plc: new PlcDidDocumentResolver(), 23 + web: new WebDidDocumentResolver(), 24 + }, 25 + }) 26 27 /** 28 * Cleans the handle of @ and some other unicode characters that used to show up when copied from the profile 29 * @param handle {string} 30 * @returns {string} 31 */ 32 + const cleanHandle = handle => 33 + handle 34 + .replace('@', '') 35 + .trim() 36 + .replace(/[\u202A\u202C\u200E\u200F\u2066-\u2069]/g, '') 37 38 /** 39 * Convince helper to resolve a handle to a did and then find the PDS url from the did document. ··· 42 * @returns {Promise<{usersDid: string, pds: string}>} 43 */ 44 async function handleAndPDSResolver(handle) { 45 + let usersDid = null 46 + if (handle.startsWith('did:')) { 47 + usersDid = handle 48 + } else { 49 + const cleanedHandle = cleanHandle(handle) 50 + usersDid = await handleResolver.resolve(cleanedHandle) 51 + } 52 + const didDoc = await docResolver.resolve(usersDid) 53 54 + let pds 55 + try { 56 + pds = didDoc.service?.filter(s => s.type === 'AtprotoPersonalDataServer')[0].serviceEndpoint 57 + } catch (error) { 58 + throw new Error('Could not find a PDS in the DID document.') 59 + } 60 + return { usersDid, pds } 61 } 62 63 /** 64 * Fetches the DID Web from the .well-known/did.json endpoint of the server. ··· 67 * @returns {Promise<*>} 68 */ 69 async function fetchPDSMooverDIDWeb(baseUrl) { 70 + const response = await fetch(`${baseUrl}/.well-known/did.json`) 71 + if (!response.ok) { 72 + throw new Error(`Failed to fetch DID document: ${response.status}`) 73 + } 74 + const didDoc = await response.json() 75 + return didDoc.id 76 } 77 78 + export { handleResolver, docResolver, cleanHandle, handleAndPDSResolver, fetchPDSMooverDIDWeb }
+221 -225
packages/moover/lib/backup.js
··· 1 - import {Client, CredentialManager, ok} from '@atcute/client'; 2 - import {handleAndPDSResolver} from './atprotoUtils.js'; 3 //Shows as unused, but is used in the return types 4 - import {ComPdsmooverBackupDescribeServer} from '@pds-moover/lexicons'; 5 6 /** 7 * JSDoc type-only import to avoid runtime import errors in the browser. 8 * @typedef {import('@atcute/lexicons').InferXRPCBodyOutput} InferXRPCBodyOutput 9 */ 10 - 11 12 /** 13 * Logic to sign up and manage backups for pdsmoover.com (or your own selfhosted instance) 14 */ 15 class BackupService { 16 /** 17 * 18 - * @param backupDidWeb {string} - The did:web for the xrpc service for backups, defaults to did:web:pdsmoover.com 19 */ 20 - constructor(backupDidWeb = 'did:web:pdsmoover.com') { 21 - /** 22 - * 23 - * @type {Client} 24 - */ 25 - this.atCuteClient = null; 26 - /** 27 - * 28 - * @type {CredentialManager} 29 - */ 30 - this.atCuteCredentialManager = null; 31 - 32 - /** 33 - * The did:web for the xrpc service for backups, defaults to pdsmoover.com 34 - * @type {string} 35 - */ 36 - this.backupDidWeb = backupDidWeb; 37 - } 38 - 39 - 40 /** 41 - * Logs in and returns the backup status. 42 - * To use the rest of the BackupService, it is assumed that this has ran first, 43 - * and the user has successfully signed up. A successful login is a returned null if the user has not signed up. 44 - * or the backup status if they are 45 * 46 - * If the server requires 2FA, 47 - * it will throw with error.error === 'AuthFactorTokenRequired'. 48 - * @param identifier {string} handle or did 49 - * @param password {string} 50 - * @param {function|null} onStatus - a function that takes a string used to update the UI. 51 - * Like (status) => console.log(status) 52 - * @param twoFactorCode {string|null} 53 - * 54 - * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>|null>} 55 */ 56 - async loginAndStatus(identifier, password, onStatus = null, twoFactorCode = null) { 57 - let {pds} = await handleAndPDSResolver(identifier); 58 59 60 - const manager = new CredentialManager({ 61 - service: pds 62 - }); 63 64 65 - const rpc = new Client({ 66 - handler: manager, 67 - proxy: { 68 - did: this.backupDidWeb, 69 - serviceId: '#repo_backup' 70 - } 71 - }); 72 73 - try { 74 - if (onStatus) onStatus('Signing in…'); 75 76 - let loginInput = { 77 - identifier, 78 - password, 79 80 - }; 81 - if (twoFactorCode) { 82 - loginInput.code = twoFactorCode; 83 - } 84 - await manager.login(loginInput); 85 - 86 - 87 - // Make the client/manager available regardless of repo status so we can sign up if needed. 88 - this.atCuteClient = rpc; 89 - this.atCuteCredentialManager = manager; 90 - 91 - if (onStatus) onStatus('Checking backup status'); 92 - const result = await rpc.get('com.pdsmoover.backup.getRepoStatus', { 93 - params: { 94 - did: manager.session.did.toString() 95 - } 96 - }); 97 - if (result.ok) { 98 - return result.data; 99 - } else { 100 - switch (result.data.error) { 101 - case 'RepoNotFound': 102 - return null; 103 - default: 104 - throw result.data.error; 105 - } 106 107 - } 108 - } catch (err) { 109 - throw err; 110 } 111 } 112 113 - /** 114 - * Signs the user up for backups with the service 115 - * @param onStatus 116 - * @returns {Promise<void>} 117 - */ 118 - async signUp(onStatus = null) { 119 - if (!this.atCuteClient || !this.atCuteCredentialManager) { 120 - throw new Error('Not signed in'); 121 - } 122 - if (onStatus) onStatus('Creating backup registration…'); 123 - await ok( 124 - this.atCuteClient.post('com.pdsmoover.backup.signUp', { 125 - as: null, 126 - }) 127 - ); 128 - if (onStatus) onStatus('Backup registration complete'); 129 - //No return if successful 130 } 131 132 - /** 133 - * Requests a PLC token to be sent to the user's email, needed to add a new rotation key 134 - * @returns {Promise<void>} 135 - */ 136 - async requestAPlcToken() { 137 - if (!this.atCuteClient || !this.atCuteCredentialManager) { 138 - throw new Error('Not signed in'); 139 - } 140 - const rpc = new Client({ 141 - handler: this.atCuteCredentialManager, 142 - }); 143 - 144 - let response = await rpc.post('com.atproto.identity.requestPlcOperationSignature', { 145 - as: null, 146 - }); 147 - if (!response.ok) { 148 - throw new Error(response.data?.message || 'Failed to request PLC token'); 149 - } 150 } 151 - 152 - /** 153 - * Adds a new rotation to the users did document. Assumes you are already signed in. 154 - * 155 - * WARNING: This will overwrite any existing rotation keys with the new one at the top, and the PDS key as the second one 156 - * @param plcToken {string} - PLC token from the user's email that was sent from requestAPlcToken 157 - * @param rotationKey {string} - The new rotation key to add to the user's did document 158 - * @returns {Promise<void>} 159 - */ 160 - async addANewRotationKey(plcToken, rotationKey) { 161 - if (!this.atCuteClient || !this.atCuteCredentialManager) { 162 - throw new Error('Not signed in'); 163 - } 164 - 165 - 166 - const rpc = new Client({ 167 - handler: this.atCuteCredentialManager, 168 - }); 169 170 - let getDidCredentials = await rpc.get('com.atproto.identity.getRecommendedDidCredentials'); 171 172 - if (getDidCredentials.ok) { 173 - const pdsProvidedRotationKeys = getDidCredentials.data.rotationKeys ?? []; 174 - const updatedRotationKeys = [rotationKey, ...pdsProvidedRotationKeys]; 175 176 - const credentials = { 177 - ...getDidCredentials.data, 178 - rotationKeys: updatedRotationKeys, 179 - }; 180 181 - const signDocRes = await rpc.post('com.atproto.identity.signPlcOperation', { 182 - input: { 183 - token: plcToken, 184 - ...credentials, 185 - } 186 - }); 187 188 - if (signDocRes.ok) { 189 - const submitDocRes = await rpc.post('com.atproto.identity.submitPlcOperation', { 190 - input: signDocRes.data, 191 - as: null, 192 - }); 193 194 - if (!submitDocRes.ok) { 195 - throw new Error(submitDocRes.data?.message || 'Failed to submit PLC operation'); 196 - } 197 198 - } else { 199 - throw new Error(signDocRes.data?.message || 'Failed to sign PLC operation'); 200 - } 201 202 203 - } else { 204 - throw new Error(getDidCredentials.data?.message || 'Failed to get status'); 205 } 206 } 207 208 209 - /** 210 - * 211 - * Gets the current status of the user's backup repository. 212 - * 213 - * @param onStatus {function|null} - a function that takes a string used to update the UI. 214 - * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>>} 215 - */ 216 - async getUsersRepoStatus(onStatus = null) { 217 - if (!this.atCuteClient || !this.atCuteCredentialManager) { 218 - throw new Error('Not signed in'); 219 - } 220 - if (onStatus) onStatus('Refreshing backup status…'); 221 - const result = await this.atCuteClient.get('com.pdsmoover.backup.getRepoStatus', { 222 - params: {did: this.atCuteCredentialManager.session.did.toString()} 223 - }); 224 - if (result.ok) { 225 - return result.data; 226 - } else { 227 - throw new Error(result.data?.message || 'Failed to get status'); 228 } 229 } 230 231 - /** 232 - * Requests a backup to be run immediately for the signed-in user. Usually does, depend on the server's backup queue 233 - * @param onStatus 234 - * @returns {Promise<boolean>} 235 - */ 236 - async runBackupNow(onStatus = null) { 237 - if (!this.atCuteClient || !this.atCuteCredentialManager) { 238 - throw new Error('Not signed in'); 239 - } 240 - if (onStatus) onStatus('Requesting backup…'); 241 - const res = await this.atCuteClient.post('com.pdsmoover.backup.requestBackup', {as: null, data: {}}); 242 - if (res.ok) { 243 - if (onStatus) onStatus('Backup requested.'); 244 - return true; 245 - } else { 246 - const err = res.data; 247 - if (err?.error === 'Timeout') { 248 - throw {error: 'Timeout', message: err?.message || 'Please wait a few minutes before requesting again.'}; 249 - } 250 - throw new Error(err?.message || 'Failed to request backup'); 251 - } 252 } 253 - 254 - /** 255 - * Remove (delete) the signed-in user's backup repository. this also deletes all the user's backup data. 256 - * @param onStatus 257 - * @returns {Promise<boolean>} 258 - */ 259 - async removeRepo(onStatus = null) { 260 - if (!this.atCuteClient || !this.atCuteCredentialManager) { 261 - throw new Error('Not signed in'); 262 - } 263 - if (onStatus) onStatus('Deleting backup repository…'); 264 - const res = await this.atCuteClient.post('com.pdsmoover.backup.removeRepo', {as: null, data: {}}); 265 - if (res.ok) { 266 - if (onStatus) onStatus('Backup repository deleted.'); 267 - return true; 268 - } else { 269 - const err = res.data; 270 - throw new Error(err?.message || 'Failed to delete backup repository'); 271 - } 272 } 273 } 274 275 - 276 - export {BackupService};
··· 1 + import { Client, CredentialManager, ok } from '@atcute/client' 2 + import { handleAndPDSResolver } from './atprotoUtils.js' 3 //Shows as unused, but is used in the return types 4 + import { ComPdsmooverBackupDescribeServer } from '@pds-moover/lexicons' 5 6 /** 7 * JSDoc type-only import to avoid runtime import errors in the browser. 8 * @typedef {import('@atcute/lexicons').InferXRPCBodyOutput} InferXRPCBodyOutput 9 */ 10 11 /** 12 * Logic to sign up and manage backups for pdsmoover.com (or your own selfhosted instance) 13 */ 14 class BackupService { 15 + /** 16 + * 17 + * @param backupDidWeb {string} - The did:web for the xrpc service for backups, defaults to did:web:pdsmoover.com 18 + */ 19 + constructor(backupDidWeb = 'did:web:pdsmoover.com') { 20 /** 21 * 22 + * @type {Client} 23 */ 24 + this.atCuteClient = null 25 /** 26 * 27 + * @type {CredentialManager} 28 */ 29 + this.atCuteCredentialManager = null 30 31 + /** 32 + * The did:web for the xrpc service for backups, defaults to pdsmoover.com 33 + * @type {string} 34 + */ 35 + this.backupDidWeb = backupDidWeb 36 + } 37 38 + /** 39 + * Logs in and returns the backup status. 40 + * To use the rest of the BackupService, it is assumed that this has ran first, 41 + * and the user has successfully signed up. A successful login is a returned null if the user has not signed up. 42 + * or the backup status if they are 43 + * 44 + * If the server requires 2FA, 45 + * it will throw with error.error === 'AuthFactorTokenRequired'. 46 + * @param identifier {string} handle or did 47 + * @param password {string} 48 + * @param {function|null} onStatus - a function that takes a string used to update the UI. 49 + * Like (status) => console.log(status) 50 + * @param twoFactorCode {string|null} 51 + * 52 + * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>|null>} 53 + */ 54 + async loginAndStatus(identifier, password, onStatus = null, twoFactorCode = null) { 55 + let { pds } = await handleAndPDSResolver(identifier) 56 57 + const manager = new CredentialManager({ 58 + service: pds, 59 + }) 60 61 + const rpc = new Client({ 62 + handler: manager, 63 + proxy: { 64 + did: this.backupDidWeb, 65 + serviceId: '#repo_backup', 66 + }, 67 + }) 68 69 + try { 70 + if (onStatus) onStatus('Signing in…') 71 72 + let loginInput = { 73 + identifier, 74 + password, 75 + } 76 + if (twoFactorCode) { 77 + loginInput.code = twoFactorCode 78 + } 79 + await manager.login(loginInput) 80 81 + // Make the client/manager available regardless of repo status so we can sign up if needed. 82 + this.atCuteClient = rpc 83 + this.atCuteCredentialManager = manager 84 85 + if (onStatus) onStatus('Checking backup status') 86 + const result = await rpc.get('com.pdsmoover.backup.getRepoStatus', { 87 + params: { 88 + did: manager.session.did.toString(), 89 + }, 90 + }) 91 + if (result.ok) { 92 + return result.data 93 + } else { 94 + switch (result.data.error) { 95 + case 'RepoNotFound': 96 + return null 97 + default: 98 + throw result.data.error 99 } 100 + } 101 + } catch (err) { 102 + throw err 103 } 104 + } 105 106 + /** 107 + * Signs the user up for backups with the service 108 + * @param onStatus 109 + * @returns {Promise<void>} 110 + */ 111 + async signUp(onStatus = null) { 112 + if (!this.atCuteClient || !this.atCuteCredentialManager) { 113 + throw new Error('Not signed in') 114 } 115 + if (onStatus) onStatus('Creating backup registration…') 116 + await ok( 117 + this.atCuteClient.post('com.pdsmoover.backup.signUp', { 118 + as: null, 119 + }), 120 + ) 121 + if (onStatus) onStatus('Backup registration complete') 122 + //No return if successful 123 + } 124 125 + /** 126 + * Requests a PLC token to be sent to the user's email, needed to add a new rotation key 127 + * @returns {Promise<void>} 128 + */ 129 + async requestAPlcToken() { 130 + if (!this.atCuteClient || !this.atCuteCredentialManager) { 131 + throw new Error('Not signed in') 132 } 133 + const rpc = new Client({ 134 + handler: this.atCuteCredentialManager, 135 + }) 136 137 + let response = await rpc.post('com.atproto.identity.requestPlcOperationSignature', { 138 + as: null, 139 + }) 140 + if (!response.ok) { 141 + throw new Error(response.data?.message || 'Failed to request PLC token') 142 + } 143 + } 144 145 + /** 146 + * Adds a new rotation to the users did document. Assumes you are already signed in. 147 + * 148 + * WARNING: This will overwrite any existing rotation keys with the new one at the top, and the PDS key as the second one 149 + * @param plcToken {string} - PLC token from the user's email that was sent from requestAPlcToken 150 + * @param rotationKey {string} - The new rotation key to add to the user's did document 151 + * @returns {Promise<void>} 152 + */ 153 + async addANewRotationKey(plcToken, rotationKey) { 154 + if (!this.atCuteClient || !this.atCuteCredentialManager) { 155 + throw new Error('Not signed in') 156 + } 157 158 + const rpc = new Client({ 159 + handler: this.atCuteCredentialManager, 160 + }) 161 162 + let getDidCredentials = await rpc.get('com.atproto.identity.getRecommendedDidCredentials') 163 164 + if (getDidCredentials.ok) { 165 + const pdsProvidedRotationKeys = getDidCredentials.data.rotationKeys ?? [] 166 + const updatedRotationKeys = [rotationKey, ...pdsProvidedRotationKeys] 167 168 + const credentials = { 169 + ...getDidCredentials.data, 170 + rotationKeys: updatedRotationKeys, 171 + } 172 173 + const signDocRes = await rpc.post('com.atproto.identity.signPlcOperation', { 174 + input: { 175 + token: plcToken, 176 + ...credentials, 177 + }, 178 + }) 179 180 + if (signDocRes.ok) { 181 + const submitDocRes = await rpc.post('com.atproto.identity.submitPlcOperation', { 182 + input: signDocRes.data, 183 + as: null, 184 + }) 185 186 + if (!submitDocRes.ok) { 187 + throw new Error(submitDocRes.data?.message || 'Failed to submit PLC operation') 188 } 189 + } else { 190 + throw new Error(signDocRes.data?.message || 'Failed to sign PLC operation') 191 + } 192 + } else { 193 + throw new Error(getDidCredentials.data?.message || 'Failed to get status') 194 } 195 + } 196 197 + /** 198 + * 199 + * Gets the current status of the user's backup repository. 200 + * 201 + * @param onStatus {function|null} - a function that takes a string used to update the UI. 202 + * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>>} 203 + */ 204 + async getUsersRepoStatus(onStatus = null) { 205 + if (!this.atCuteClient || !this.atCuteCredentialManager) { 206 + throw new Error('Not signed in') 207 + } 208 + if (onStatus) onStatus('Refreshing backup status…') 209 + const result = await this.atCuteClient.get('com.pdsmoover.backup.getRepoStatus', { 210 + params: { did: this.atCuteCredentialManager.session.did.toString() }, 211 + }) 212 + if (result.ok) { 213 + return result.data 214 + } else { 215 + throw new Error(result.data?.message || 'Failed to get status') 216 + } 217 + } 218 219 + /** 220 + * Requests a backup to be run immediately for the signed-in user. Usually does, depend on the server's backup queue 221 + * @param onStatus 222 + * @returns {Promise<boolean>} 223 + */ 224 + async runBackupNow(onStatus = null) { 225 + if (!this.atCuteClient || !this.atCuteCredentialManager) { 226 + throw new Error('Not signed in') 227 + } 228 + if (onStatus) onStatus('Requesting backup…') 229 + const res = await this.atCuteClient.post('com.pdsmoover.backup.requestBackup', { 230 + as: null, 231 + data: {}, 232 + }) 233 + if (res.ok) { 234 + if (onStatus) onStatus('Backup requested.') 235 + return true 236 + } else { 237 + const err = res.data 238 + if (err?.error === 'Timeout') { 239 + throw { 240 + error: 'Timeout', 241 + message: err?.message || 'Please wait a few minutes before requesting again.', 242 } 243 + } 244 + throw new Error(err?.message || 'Failed to request backup') 245 } 246 + } 247 248 + /** 249 + * Remove (delete) the signed-in user's backup repository. this also deletes all the user's backup data. 250 + * @param onStatus 251 + * @returns {Promise<boolean>} 252 + */ 253 + async removeRepo(onStatus = null) { 254 + if (!this.atCuteClient || !this.atCuteCredentialManager) { 255 + throw new Error('Not signed in') 256 } 257 + if (onStatus) onStatus('Deleting backup repository…') 258 + const res = await this.atCuteClient.post('com.pdsmoover.backup.removeRepo', { 259 + as: null, 260 + data: {}, 261 + }) 262 + if (res.ok) { 263 + if (onStatus) onStatus('Backup repository deleted.') 264 + return true 265 + } else { 266 + const err = res.data 267 + throw new Error(err?.message || 'Failed to delete backup repository') 268 } 269 + } 270 } 271 272 + export { BackupService }
+5
packages/moover/lib/lexicons/blue.ts
···
··· 1 + /* 2 + * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 + */ 4 + 5 + export * as microcosm from './blue/microcosm.js'
+5
packages/moover/lib/lexicons/blue/microcosm.ts
···
··· 1 + /* 2 + * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 + */ 4 + 5 + export * as identity from './microcosm/identity.js'
+5
packages/moover/lib/lexicons/blue/microcosm/identity.ts
···
··· 1 + /* 2 + * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 + */ 4 + 5 + export * as resolveMiniDoc from './identity/resolveMiniDoc.js'
+30
packages/moover/lib/lexicons/blue/microcosm/identity/resolveMiniDoc.defs.ts
···
··· 1 + /* 2 + * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 + */ 4 + 5 + import { l } from '@atproto/lex' 6 + 7 + const $nsid = 'blue.microcosm.identity.resolveMiniDoc' 8 + 9 + export { $nsid } 10 + 11 + /** Like [com.atproto.identity.resolveIdentity](https://docs.bsky.app/docs/api/com-atproto-identity-resolve-identity) but instead of the full `didDoc` it returns an atproto-relevant subset. */ 12 + const main = l.query( 13 + $nsid, 14 + l.params({ identifier: l.string({ format: 'at-identifier' }) }), 15 + l.jsonPayload({ 16 + did: l.string({ format: 'did' }), 17 + pds: l.string({ format: 'uri' }), 18 + handle: l.string({ format: 'handle' }), 19 + signing_key: l.string(), 20 + }), 21 + ) 22 + export { main } 23 + 24 + export type Params = l.InferMethodParams<typeof main> 25 + export type Output = l.InferMethodOutput<typeof main> 26 + export type OutputBody = l.InferMethodOutputBody<typeof main> 27 + 28 + export const $lxm = main.nsid, 29 + $params = main.parameters, 30 + $output = main.output
+6
packages/moover/lib/lexicons/blue/microcosm/identity/resolveMiniDoc.ts
···
··· 1 + /* 2 + * THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. 3 + */ 4 + 5 + export * from './resolveMiniDoc.defs.js' 6 + export * as $defs from './resolveMiniDoc.defs.js'
+7 -16
packages/moover/lib/main.js
··· 1 - import {Migrator} from './pdsmoover.js'; 2 - import {MissingBlobs} from './missingBlobs.js'; 3 - import {BackupService} from './backup.js'; 4 - import {PlcOps} from './plc-ops.js'; 5 - import {Restore} from './restore.js'; 6 - import {handleAndPDSResolver} from './atprotoUtils.js'; 7 8 - export { 9 - Migrator, 10 - MissingBlobs, 11 - BackupService, 12 - PlcOps, 13 - Restore, 14 - handleAndPDSResolver, 15 - 16 - } 17 -
··· 1 + import { Migrator } from './pdsmoover.js' 2 + import { MissingBlobs } from './missingBlobs.js' 3 + import { BackupService } from './backup.js' 4 + import { PlcOps } from './plc-ops.js' 5 + import { Restore } from './restore.js' 6 + import { handleAndPDSResolver } from './atprotoUtils.js' 7 8 + export { Migrator, MissingBlobs, BackupService, PlcOps, Restore, handleAndPDSResolver }
+162 -166
packages/moover/lib/missingBlobs.js
··· 1 - import {AtpAgent} from '@atproto/api'; 2 - import {handleAndPDSResolver} from './atprotoUtils.js'; 3 - 4 5 /** 6 * Class to help find missing blobs from the did's previous PDS and import them into the current PDS 7 */ 8 class MissingBlobs { 9 - 10 - constructor() { 11 - /** 12 - * The user's current PDS agent 13 - * @type {AtpAgent} 14 - */ 15 - this.currentPdsAgent = null; 16 - /** 17 - * The user's old PDS agent 18 - * @type {AtpAgent} 19 - */ 20 - this.oldPdsAgent = null; 21 - /** 22 - * the user's did 23 - * @type {string|null} 24 - */ 25 - this.did = null; 26 - /** 27 - * The user's current PDS url 28 - * @type {null} 29 - */ 30 - this.currentPdsUrl = null; 31 - /** 32 - * A list of the missing cids blobs from the old PDS. In this case if a retry upload fails it gets put in this array for the ui 33 - * @type {string[]} 34 - */ 35 - this.missingBlobs = []; 36 - 37 - } 38 - 39 /** 40 - * Logs the user into the current PDS and gets the account status 41 - * @param handle {string} 42 - * @param password {string} 43 - * @param twoFactorCode {string|null} 44 - * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 45 */ 46 - async currentAgentLogin( 47 - handle, 48 - password, 49 - twoFactorCode = null, 50 - ) { 51 - let {usersDid, pds} = await handleAndPDSResolver(handle); 52 - this.did = usersDid; 53 - this.currentPdsUrl = pds; 54 - const agent = new AtpAgent({ 55 - service: pds, 56 - }); 57 - 58 - if (twoFactorCode === null) { 59 - await agent.login({identifier: usersDid, password}); 60 - } else { 61 - await agent.login({identifier: usersDid, password: password, authFactorToken: twoFactorCode}); 62 - } 63 64 - this.currentPdsAgent = agent; 65 66 - const result = await agent.com.atproto.server.checkAccountStatus(); 67 - const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({ 68 - limit: 10, 69 - }); 70 - return {accountStatus: result.data, missingBlobsCount: missingBlobs.data.blobs.length}; 71 } 72 73 - /** 74 - * Logs into the old PDS and gets the account status. 75 - * Does not need a handle 76 - * since it is assumed the user has already logged in with the current PDS and we are using their did 77 - * @param password {string} 78 - * @param twoFactorCode {string|null} 79 - * @param pdsUrl {string|null} - If you know the url of the old PDS you can pass it in here. If not it will be guessed at from plc ops 80 - * @returns {Promise<void>} 81 - */ 82 - async oldAgentLogin( 83 - password, 84 - twoFactorCode = null, 85 - pdsUrl = null, 86 - ) { 87 - let oldPds = null; 88 89 - if (pdsUrl === null) { 90 - const response = await fetch(`https://plc.directory/${this.did}/log`); 91 - let auditLog = await response.json(); 92 - auditLog = auditLog.reverse(); 93 - let debugCount = 0; 94 - for (const entry of auditLog) { 95 - console.log(`Loop: ${debugCount++}`); 96 - console.log(entry); 97 - if (entry.services) { 98 - if (entry.services.atproto_pds) { 99 - if (entry.services.atproto_pds.type === 'AtprotoPersonalDataServer') { 100 - const pds = entry.services.atproto_pds.endpoint; 101 - console.log(`Found PDS: ${pds}`); 102 - if (pds.toLowerCase() !== this.currentPdsUrl.toLowerCase()) { 103 - oldPds = pds; 104 - break; 105 - } 106 - } 107 - } 108 - } 109 - } 110 - if (oldPds === null) { 111 - throw new Error('Could not find your old PDS'); 112 - } 113 - } else { 114 - oldPds = pdsUrl; 115 - } 116 117 - const agent = new AtpAgent({ 118 - service: oldPds, 119 - }); 120 121 - if (twoFactorCode === null) { 122 - await agent.login({identifier: this.did, password}); 123 - } else { 124 - await agent.login({identifier: this.did, password: password, authFactorToken: twoFactorCode}); 125 } 126 - this.oldPdsAgent = agent; 127 } 128 129 - /** 130 - * Gets the missing blobs from the old PDS and uploads them to the current PDS 131 - * @param statusUpdateHandler {function} - A function to update the status of the migration. This is useful for showing the user the progress of the migration 132 - * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 133 - */ 134 - async migrateMissingBlobs(statusUpdateHandler) { 135 - if (this.currentPdsAgent === null) { 136 - throw new Error('Current PDS agent is not set'); 137 - } 138 - if (this.oldPdsAgent === null) { 139 - throw new Error('Old PDS agent is not set'); 140 - } 141 - statusUpdateHandler('Starting to import blobs...'); 142 143 - let totalMissingBlobs = 0; 144 - let missingBlobCursor = undefined; 145 - let missingUploadedBlobs = 0; 146 - 147 - do { 148 - 149 - const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({ 150 - cursor: missingBlobCursor, 151 - limit: 1000, 152 - }); 153 - totalMissingBlobs += missingBlobs.data.blobs.length; 154 - 155 - for (const recordBlob of missingBlobs.data.blobs) { 156 - try { 157 158 - const blobRes = await this.oldPdsAgent.com.atproto.sync.getBlob({ 159 - did: this.did, 160 - cid: recordBlob.cid, 161 - }); 162 - let result = await this.currentPdsAgent.com.atproto.repo.uploadBlob(blobRes.data, { 163 - encoding: blobRes.headers['content-type'], 164 - }); 165 166 - if (result.status === 429) { 167 - statusUpdateHandler(`You are being rate limited. Will need to try again later to get the rest of the blobs. Migrated blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 168 - } 169 170 - if (missingUploadedBlobs % 2 === 0) { 171 - statusUpdateHandler(`Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs} (The total may increase as we find more)`); 172 - } 173 - missingUploadedBlobs++; 174 - } catch (error) { 175 - console.error(error); 176 - this.missingBlobs.push(recordBlob.cid); 177 - } 178 - } 179 - missingBlobCursor = missingBlobs.data.cursor; 180 - } while (missingBlobCursor); 181 182 - const accountStatus = await this.currentPdsAgent.com.atproto.server.checkAccountStatus(); 183 - const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({ 184 - limit: 10, 185 - }); 186 - return {accountStatus: accountStatus.data, missingBlobsCount: missingBlobs.data.blobs.length}; 187 188 189 - } 190 191 } 192 193 - export {MissingBlobs};
··· 1 + import { AtpAgent } from '@atproto/api' 2 + import { handleAndPDSResolver } from './atprotoUtils.js' 3 4 /** 5 * Class to help find missing blobs from the did's previous PDS and import them into the current PDS 6 */ 7 class MissingBlobs { 8 + constructor() { 9 /** 10 + * The user's current PDS agent 11 + * @type {AtpAgent} 12 */ 13 + this.currentPdsAgent = null 14 + /** 15 + * The user's old PDS agent 16 + * @type {AtpAgent} 17 + */ 18 + this.oldPdsAgent = null 19 + /** 20 + * the user's did 21 + * @type {string|null} 22 + */ 23 + this.did = null 24 + /** 25 + * The user's current PDS url 26 + * @type {null} 27 + */ 28 + this.currentPdsUrl = null 29 + /** 30 + * A list of the missing cids blobs from the old PDS. In this case if a retry upload fails it gets put in this array for the ui 31 + * @type {string[]} 32 + */ 33 + this.missingBlobs = [] 34 + } 35 36 + /** 37 + * Logs the user into the current PDS and gets the account status 38 + * @param handle {string} 39 + * @param password {string} 40 + * @param twoFactorCode {string|null} 41 + * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 42 + */ 43 + async currentAgentLogin(handle, password, twoFactorCode = null) { 44 + let { usersDid, pds } = await handleAndPDSResolver(handle) 45 + this.did = usersDid 46 + this.currentPdsUrl = pds 47 + const agent = new AtpAgent({ 48 + service: pds, 49 + }) 50 51 + if (twoFactorCode === null) { 52 + await agent.login({ identifier: usersDid, password }) 53 + } else { 54 + await agent.login({ 55 + identifier: usersDid, 56 + password: password, 57 + authFactorToken: twoFactorCode, 58 + }) 59 } 60 61 + this.currentPdsAgent = agent 62 63 + const result = await agent.com.atproto.server.checkAccountStatus() 64 + const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({ 65 + limit: 10, 66 + }) 67 + return { accountStatus: result.data, missingBlobsCount: missingBlobs.data.blobs.length } 68 + } 69 70 + /** 71 + * Logs into the old PDS and gets the account status. 72 + * Does not need a handle 73 + * since it is assumed the user has already logged in with the current PDS and we are using their did 74 + * @param password {string} 75 + * @param twoFactorCode {string|null} 76 + * @param pdsUrl {string|null} - If you know the url of the old PDS you can pass it in here. If not it will be guessed at from plc ops 77 + * @returns {Promise<void>} 78 + */ 79 + async oldAgentLogin(password, twoFactorCode = null, pdsUrl = null) { 80 + let oldPds = null 81 82 + if (pdsUrl === null) { 83 + const response = await fetch(`https://plc.directory/${this.did}/log`) 84 + let auditLog = await response.json() 85 + auditLog = auditLog.reverse() 86 + let debugCount = 0 87 + for (const entry of auditLog) { 88 + console.log(`Loop: ${debugCount++}`) 89 + console.log(entry) 90 + if (entry.services) { 91 + if (entry.services.atproto_pds) { 92 + if (entry.services.atproto_pds.type === 'AtprotoPersonalDataServer') { 93 + const pds = entry.services.atproto_pds.endpoint 94 + console.log(`Found PDS: ${pds}`) 95 + if (pds.toLowerCase() !== this.currentPdsUrl.toLowerCase()) { 96 + oldPds = pds 97 + break 98 + } 99 + } 100 + } 101 } 102 + } 103 + if (oldPds === null) { 104 + throw new Error('Could not find your old PDS') 105 + } 106 + } else { 107 + oldPds = pdsUrl 108 } 109 110 + const agent = new AtpAgent({ 111 + service: oldPds, 112 + }) 113 114 + if (twoFactorCode === null) { 115 + await agent.login({ identifier: this.did, password }) 116 + } else { 117 + await agent.login({ 118 + identifier: this.did, 119 + password: password, 120 + authFactorToken: twoFactorCode, 121 + }) 122 + } 123 + this.oldPdsAgent = agent 124 + } 125 126 + /** 127 + * Gets the missing blobs from the old PDS and uploads them to the current PDS 128 + * @param statusUpdateHandler {function} - A function to update the status of the migration. This is useful for showing the user the progress of the migration 129 + * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 130 + */ 131 + async migrateMissingBlobs(statusUpdateHandler) { 132 + if (this.currentPdsAgent === null) { 133 + throw new Error('Current PDS agent is not set') 134 + } 135 + if (this.oldPdsAgent === null) { 136 + throw new Error('Old PDS agent is not set') 137 + } 138 + statusUpdateHandler('Starting to import blobs...') 139 140 + let totalMissingBlobs = 0 141 + let missingBlobCursor = undefined 142 + let missingUploadedBlobs = 0 143 144 + do { 145 + const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({ 146 + cursor: missingBlobCursor, 147 + limit: 1000, 148 + }) 149 + totalMissingBlobs += missingBlobs.data.blobs.length 150 151 + for (const recordBlob of missingBlobs.data.blobs) { 152 + try { 153 + const blobRes = await this.oldPdsAgent.com.atproto.sync.getBlob({ 154 + did: this.did, 155 + cid: recordBlob.cid, 156 + }) 157 + let result = await this.currentPdsAgent.com.atproto.repo.uploadBlob(blobRes.data, { 158 + encoding: blobRes.headers['content-type'], 159 + }) 160 161 + if (result.status === 429) { 162 + statusUpdateHandler( 163 + `You are being rate limited. Will need to try again later to get the rest of the blobs. Migrated blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`, 164 + ) 165 + } 166 167 + if (missingUploadedBlobs % 2 === 0) { 168 + statusUpdateHandler( 169 + `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs} (The total may increase as we find more)`, 170 + ) 171 + } 172 + missingUploadedBlobs++ 173 + } catch (error) { 174 + console.error(error) 175 + this.missingBlobs.push(recordBlob.cid) 176 + } 177 + } 178 + missingBlobCursor = missingBlobs.data.cursor 179 + } while (missingBlobCursor) 180 181 + const accountStatus = await this.currentPdsAgent.com.atproto.server.checkAccountStatus() 182 + const missingBlobs = await this.currentPdsAgent.com.atproto.repo.listMissingBlobs({ 183 + limit: 10, 184 + }) 185 + return { accountStatus: accountStatus.data, missingBlobsCount: missingBlobs.data.blobs.length } 186 + } 187 } 188 189 + export { MissingBlobs }
+392 -341
packages/moover/lib/pdsmoover.js
··· 1 - import {docResolver, cleanHandle, handleResolver} from './atprotoUtils.js'; 2 - import {AtpAgent} from '@atproto/api'; 3 - 4 5 function safeStatusUpdate(statusUpdateHandler, status) { 6 - if (statusUpdateHandler) { 7 - statusUpdateHandler(status); 8 - } 9 } 10 11 /** ··· 13 * On pdsmoover.com this is the logic for the MOOver 14 */ 15 class Migrator { 16 - constructor() { 17 - /** @type {AtpAgent} */ 18 - this.oldAgent = null; 19 - /** @type {AtpAgent} */ 20 - this.newAgent = null; 21 - /** @type {[string]} */ 22 - this.missingBlobs = []; 23 - //State for reruns 24 - /** @type {boolean} */ 25 - this.createNewAccount = true; 26 - /** @type {boolean} */ 27 - this.migrateRepo = true; 28 - /** @type {boolean} */ 29 - this.migrateBlobs = true; 30 - /** @type {boolean} */ 31 - this.migrateMissingBlobs = true; 32 - /** @type {boolean} */ 33 - this.migratePrefs = true; 34 - /** @type {boolean} */ 35 - this.migratePlcRecord = true; 36 - } 37 38 - /** 39 - * This migrator is pretty cut and dry and makes a few assumptions 40 - * 1. You are using the same password between each account 41 - * 2. If this command fails for something like oauth 2fa code it throws an error and expects the same values when ran again. 42 - * 3. You can control which "actions" happen by setting the class variables to false. 43 - * 4. Each instance of the class is assumed to be for a single migration 44 - * @param {string} oldHandle - The handle you use on your old pds, something like alice.bsky.social 45 - * @param {string} password - Your password for your current login. Has to be your real password, no app password. When setting up a new account we reuse it as well for that account 46 - * @param {string} newPdsUrl - The new URL for your pds. Like https://coolnewpds.com 47 - * @param {string} newEmail - The email you want to use on the new pds (can be the same as the previous one as long as it's not already being used on the new pds) 48 - * @param {string} newHandle - The new handle you want, like alice.bsky.social, or if you already have a domain name set as a handle can use it myname.com. 49 - * @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one 50 - * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status) 51 - * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 52 - * @param verificationCode - Optional verification captcha code for account creation if the PDS requires it 53 - */ 54 - async migrate(oldHandle, password, newPdsUrl, newEmail, newHandle, inviteCode, statusUpdateHandler = null, twoFactorCode = null, verificationCode = null) { 55 - //Leaving this logic that either sets the agent to bsky.social, or the PDS since it's what I found worked best for migrations. 56 - // handleAndPDSResolver should be able to handle it, but there have been edge cases and this was what worked best 57 - oldHandle = cleanHandle(oldHandle); 58 - let oldAgent; 59 - let usersDid; 60 - //If it's a bsky handle just go with the entryway and let it sort everything 61 - if (oldHandle.endsWith('.bsky.social')) { 62 - oldAgent = new AtpAgent({service: 'https://bsky.social'}); 63 - const publicAgent = new AtpAgent({service: 'https://public.api.bsky.app'}); 64 - const resolveIdentityFromEntryway = await publicAgent.com.atproto.identity.resolveHandle({handle: oldHandle}); 65 - usersDid = resolveIdentityFromEntryway.data.did; 66 67 - } else { 68 - //Resolves the did and finds the did document for the old PDS 69 - safeStatusUpdate(statusUpdateHandler, 'Resolving old PDS'); 70 - usersDid = await handleResolver.resolve(oldHandle); 71 - const didDoc = await docResolver.resolve(usersDid); 72 - safeStatusUpdate(statusUpdateHandler, 'Resolving did document and finding your current PDS URL'); 73 74 - let oldPds; 75 - try { 76 - oldPds = didDoc.service.filter(s => s.type === 'AtprotoPersonalDataServer')[0].serviceEndpoint; 77 - } catch (error) { 78 - console.error(error); 79 - throw new Error('Could not find a PDS in the DID document.'); 80 - } 81 82 - oldAgent = new AtpAgent({ 83 - service: oldPds, 84 - }); 85 86 - } 87 88 - safeStatusUpdate(statusUpdateHandler, 'Logging you in to the old PDS'); 89 - //Login to the old PDS 90 - if (twoFactorCode === null) { 91 - await oldAgent.login({identifier: oldHandle, password}); 92 - } else { 93 - await oldAgent.login({identifier: oldHandle, password: password, authFactorToken: twoFactorCode}); 94 - } 95 96 - safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); 97 - const newAgent = new AtpAgent({service: newPdsUrl}); 98 - const newHostDesc = await newAgent.com.atproto.server.describeServer(); 99 - if (this.createNewAccount) { 100 - const newHostWebDid = newHostDesc.data.did; 101 102 - safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); 103 104 - const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ 105 - aud: newHostWebDid, 106 - lxm: 'com.atproto.server.createAccount', 107 - }); 108 - const serviceJwt = createAuthResp.data.token; 109 110 - let createAccountRequest = { 111 - did: usersDid, 112 - handle: newHandle, 113 - email: newEmail, 114 - password: password, 115 - }; 116 - if (inviteCode) { 117 - createAccountRequest.inviteCode = inviteCode; 118 - } 119 - if (verificationCode) { 120 - createAccountRequest.verificationCode = verificationCode; 121 - } 122 - const createNewAccount = await newAgent.com.atproto.server.createAccount( 123 - createAccountRequest, 124 - { 125 - headers: {authorization: `Bearer ${serviceJwt}`}, 126 - encoding: 'application/json', 127 - }); 128 129 - if (createNewAccount.data.did !== usersDid.toString()) { 130 - throw new Error('Did not create the new account with the same did as the old account'); 131 - } 132 - } 133 - safeStatusUpdate(statusUpdateHandler, 'Logging in with the new account'); 134 135 - await newAgent.login({ 136 - identifier: usersDid, 137 - password: password, 138 - }); 139 140 - if (this.migrateRepo) { 141 - safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); 142 - const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); 143 - await newAgent.com.atproto.repo.importRepo(repoRes.data, { 144 - encoding: 'application/vnd.ipld.car', 145 - }); 146 - } 147 148 - let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 149 150 - if (this.migrateBlobs) { 151 - safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 152 153 - let blobCursor = undefined; 154 - let uploadedBlobs = 0; 155 - do { 156 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 157 - 158 - const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 159 - did: usersDid, 160 - cursor: blobCursor, 161 - limit: 100, 162 - }); 163 - 164 - for (const cid of listedBlobs.data.cids) { 165 - try { 166 - const blobRes = await oldAgent.com.atproto.sync.getBlob({ 167 - did: usersDid, 168 - cid, 169 - }); 170 - await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 171 - encoding: blobRes.headers['content-type'], 172 - }); 173 - uploadedBlobs++; 174 - if (uploadedBlobs % 10 === 0) { 175 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 176 - } 177 - } catch (error) { 178 - console.error(error); 179 - } 180 - } 181 - blobCursor = listedBlobs.data.cursor; 182 - } while (blobCursor); 183 } 184 185 - if (this.migrateMissingBlobs) { 186 - newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 187 - if (newAccountStatus.data.expectedBlobs !== newAccountStatus.data.importedBlobs) { 188 - let totalMissingBlobs = newAccountStatus.data.expectedBlobs - newAccountStatus.data.importedBlobs; 189 - safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 190 - //Probably should be shared between main blob uploader, but eh 191 - let missingBlobCursor = undefined; 192 - let missingUploadedBlobs = 0; 193 - do { 194 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 195 196 - const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs({ 197 - cursor: missingBlobCursor, 198 - limit: 100, 199 - }); 200 - 201 - for (const recordBlob of missingBlobs.data.blobs) { 202 - try { 203 - 204 - const blobRes = await oldAgent.com.atproto.sync.getBlob({ 205 - did: usersDid, 206 - cid: recordBlob.cid, 207 - }); 208 - await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 209 - encoding: blobRes.headers['content-type'], 210 - }); 211 - if (missingUploadedBlobs % 10 === 0) { 212 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 213 - } 214 - missingUploadedBlobs++; 215 - } catch (error) { 216 - //TODO silently logging prob should list them so user can manually download 217 - console.error(error); 218 - this.missingBlobs.push(recordBlob.cid); 219 - } 220 - } 221 - missingBlobCursor = missingBlobs.data.cursor; 222 - } while (missingBlobCursor); 223 224 } 225 - } 226 - if (this.migratePrefs) { 227 - const prefs = await oldAgent.app.bsky.actor.getPreferences(); 228 - await newAgent.app.bsky.actor.putPreferences(prefs.data); 229 - } 230 231 - this.oldAgent = oldAgent; 232 - this.newAgent = newAgent; 233 234 - if (this.migratePlcRecord) { 235 - await oldAgent.com.atproto.identity.requestPlcOperationSignature(); 236 - safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); 237 - } 238 } 239 240 - /** 241 - * Sign and submits the PLC operation to officially migrate the account 242 - * @param {string} token - the PLC token sent in the email. If you're just wanting to run this rerun migrate with all the flags set as false except for migratePlcRecord 243 - * @param additionalRotationKeysToAdd {string[]} - additional rotation keys to add in addition to the ones provided by the new PDS. 244 - * @returns {Promise<void>} 245 - */ 246 - async signPlcOperation(token, additionalRotationKeysToAdd = []) { 247 - const getDidCredentials = 248 - await this.newAgent.com.atproto.identity.getRecommendedDidCredentials(); 249 - const pdsProvidedRotationKeys = getDidCredentials.data.rotationKeys ?? []; 250 - // Prepend any additional rotation keys (e.g., user-added keys, newly created key) so they appear above the new PDS rotation key 251 - const rotationKeys = [...(additionalRotationKeysToAdd || []), ...pdsProvidedRotationKeys]; 252 - if (!rotationKeys) { 253 - throw new Error('No rotation key provided from the new PDS'); 254 - } 255 - const credentials = { 256 - ...getDidCredentials.data, 257 - rotationKeys: rotationKeys, 258 - }; 259 260 261 - const plcOp = await this.oldAgent.com.atproto.identity.signPlcOperation({ 262 - token: token, 263 - ...credentials, 264 - }); 265 266 - await this.newAgent.com.atproto.identity.submitPlcOperation({ 267 - operation: plcOp.data.operation, 268 - }); 269 270 - await this.newAgent.com.atproto.server.activateAccount(); 271 - await this.oldAgent.com.atproto.server.deactivateAccount({}); 272 } 273 274 - /** 275 - * Using this method assumes the Migrator class was constructed new and this was called. 276 - * Find the user's previous PDS from the PLC op logs, 277 - * logs in and deactivates their old account if it was found still active. 278 - * 279 - * @param oldHandle {string} 280 - * @param oldPassword {string} 281 - * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. 282 - * Like (status) => console.log(status) 283 - * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 284 - * @returns {Promise<void>} 285 - */ 286 - async deactivateOldAccount(oldHandle, oldPassword, statusUpdateHandler = null, twoFactorCode = null) { 287 - //Leaving this logic that either sets the agent to bsky.social, or the PDS since it's what I found worked best for migrations. 288 - // handleAndPDSResolver should be able to handle it, but there have been edge cases and this was what worked best oldHandle = cleanHandle(oldHandle); 289 - let usersDid; 290 - //If it's a bsky handle just go with the entryway and let it sort everything 291 - if (oldHandle.endsWith('.bsky.social')) { 292 - const publicAgent = new AtpAgent({service: 'https://public.api.bsky.app'}); 293 - const resolveIdentityFromEntryway = await publicAgent.com.atproto.identity.resolveHandle({handle: oldHandle}); 294 - usersDid = resolveIdentityFromEntryway.data.did; 295 - } else { 296 - //Resolves the did and finds the did document for the old PDS 297 - safeStatusUpdate(statusUpdateHandler, 'Resolving did from handle'); 298 - usersDid = await handleResolver.resolve(oldHandle); 299 - } 300 301 - const didDoc = await docResolver.resolve(usersDid); 302 - let currentPds; 303 - try { 304 - currentPds = didDoc.service.filter(s => s.type === 'AtprotoPersonalDataServer')[0].serviceEndpoint; 305 - } catch (error) { 306 - console.error(error); 307 - throw new Error('Could not find a PDS in the DID document.'); 308 } 309 310 - const plcLogRequest = await fetch(`https://plc.directory/${usersDid}/log`); 311 - const plcLog = await plcLogRequest.json(); 312 - let pdsBeforeCurrent = ''; 313 - for (const log of plcLog) { 314 - try { 315 - const pds = log.services.atproto_pds.endpoint; 316 - if (pds.toLowerCase() === currentPds.toLowerCase()) { 317 - console.log('Found the PDS before the current one'); 318 - break; 319 - } 320 - pdsBeforeCurrent = pds; 321 - } catch (e) { 322 - console.log(e); 323 - } 324 - } 325 - if (pdsBeforeCurrent === '') { 326 - throw new Error('Could not find the PDS before the current one'); 327 - } 328 329 - let oldAgent = new AtpAgent({service: pdsBeforeCurrent}); 330 - safeStatusUpdate(statusUpdateHandler, `Logging you in to the old PDS: ${pdsBeforeCurrent}`); 331 - //Login to the old PDS 332 - if (twoFactorCode === null) { 333 - await oldAgent.login({identifier: oldHandle, password: oldPassword}); 334 - } else { 335 - await oldAgent.login({identifier: oldHandle, password: oldPassword, authFactorToken: twoFactorCode}); 336 - } 337 - safeStatusUpdate(statusUpdateHandler, 'Checking this isn\'t your current PDS'); 338 - if (pdsBeforeCurrent === currentPds) { 339 - throw new Error('This is your current PDS. Login to your old account username and password'); 340 - } 341 - 342 - let currentAccountStatus = await oldAgent.com.atproto.server.checkAccountStatus(); 343 - if (!currentAccountStatus.data.activated) { 344 - safeStatusUpdate(statusUpdateHandler, 'All good. Your old account is not activated.'); 345 - } 346 - safeStatusUpdate(statusUpdateHandler, 'Deactivating your OLD account'); 347 - await oldAgent.com.atproto.server.deactivateAccount({}); 348 - safeStatusUpdate(statusUpdateHandler, 'Successfully deactivated your OLD account'); 349 } 350 351 - /** 352 - * Signs the logged-in user in this.newAgent for backups with PDS MOOver. This is usually called after migrate and signPlcOperation are successful 353 - * 354 - * @param {string} didWeb 355 - * @returns {Promise<void>} 356 - */ 357 - async signUpForBackupsFromMigration(didWeb = 'did:web:pdsmoover.com') { 358 359 - //Manually grabbing the jwt and making a call with fetch cause for the life of me I could not figure out 360 - //how you used @atproto/api to make a call for proxying 361 - const url = `${this.newAgent.serviceUrl.origin}/xrpc/com.pdsmoover.backup.signUp`; 362 363 - const accessJwt = this.newAgent?.session?.accessJwt; 364 - if (!accessJwt) { 365 - throw new Error('Missing access token for authorization'); 366 - } 367 368 - const res = await fetch(url, { 369 - method: 'POST', 370 - headers: { 371 - 'Authorization': `Bearer ${accessJwt}`, 372 - 'Content-Type': 'application/json', 373 - 'Accept': 'application/json', 374 - 'atproto-proxy': `${didWeb}#repo_backup`, 375 - }, 376 - body: JSON.stringify({}), 377 - }); 378 379 - if (!res.ok) { 380 - let bodyText = ''; 381 - try { 382 - bodyText = await res.text(); 383 - } catch { 384 - } 385 - throw new Error(`Backup signup failed: ${res.status} ${res.statusText}${bodyText ? ` - ${bodyText}` : ''}`); 386 - } 387 - 388 - //No return the success is all that is needed, if there's an error it will throw 389 - } 390 } 391 392 - export {Migrator}; 393 -
··· 1 + import { docResolver, cleanHandle, handleResolver } from './atprotoUtils.js' 2 + import { AtpAgent } from '@atproto/api' 3 4 function safeStatusUpdate(statusUpdateHandler, status) { 5 + if (statusUpdateHandler) { 6 + statusUpdateHandler(status) 7 + } 8 } 9 10 /** ··· 12 * On pdsmoover.com this is the logic for the MOOver 13 */ 14 class Migrator { 15 + constructor() { 16 + /** @type {AtpAgent} */ 17 + this.oldAgent = null 18 + /** @type {AtpAgent} */ 19 + this.newAgent = null 20 + /** @type {[string]} */ 21 + this.missingBlobs = [] 22 + //State for reruns 23 + /** @type {boolean} */ 24 + this.createNewAccount = true 25 + /** @type {boolean} */ 26 + this.migrateRepo = true 27 + /** @type {boolean} */ 28 + this.migrateBlobs = true 29 + /** @type {boolean} */ 30 + this.migrateMissingBlobs = true 31 + /** @type {boolean} */ 32 + this.migratePrefs = true 33 + /** @type {boolean} */ 34 + this.migratePlcRecord = true 35 + } 36 37 + /** 38 + * This migrator is pretty cut and dry and makes a few assumptions 39 + * 1. You are using the same password between each account 40 + * 2. If this command fails for something like oauth 2fa code it throws an error and expects the same values when ran again. 41 + * 3. You can control which "actions" happen by setting the class variables to false. 42 + * 4. Each instance of the class is assumed to be for a single migration 43 + * @param {string} oldHandle - The handle you use on your old pds, something like alice.bsky.social 44 + * @param {string} password - Your password for your current login. Has to be your real password, no app password. When setting up a new account we reuse it as well for that account 45 + * @param {string} newPdsUrl - The new URL for your pds. Like https://coolnewpds.com 46 + * @param {string} newEmail - The email you want to use on the new pds (can be the same as the previous one as long as it's not already being used on the new pds) 47 + * @param {string} newHandle - The new handle you want, like alice.bsky.social, or if you already have a domain name set as a handle can use it myname.com. 48 + * @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one 49 + * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status) 50 + * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 51 + * @param verificationCode - Optional verification captcha code for account creation if the PDS requires it 52 + */ 53 + async migrate( 54 + oldHandle, 55 + password, 56 + newPdsUrl, 57 + newEmail, 58 + newHandle, 59 + inviteCode, 60 + statusUpdateHandler = null, 61 + twoFactorCode = null, 62 + verificationCode = null, 63 + ) { 64 + //Leaving this logic that either sets the agent to bsky.social, or the PDS since it's what I found worked best for migrations. 65 + // handleAndPDSResolver should be able to handle it, but there have been edge cases and this was what worked best 66 + oldHandle = cleanHandle(oldHandle) 67 + let oldAgent 68 + let usersDid 69 + //If it's a bsky handle just go with the entryway and let it sort everything 70 + if (oldHandle.endsWith('.bsky.social')) { 71 + oldAgent = new AtpAgent({ service: 'https://bsky.social' }) 72 + const publicAgent = new AtpAgent({ 73 + service: 'https://public.api.bsky.app', 74 + }) 75 + const resolveIdentityFromEntryway = await publicAgent.com.atproto.identity.resolveHandle({ 76 + handle: oldHandle, 77 + }) 78 + usersDid = resolveIdentityFromEntryway.data.did 79 + } else { 80 + //Resolves the did and finds the did document for the old PDS 81 + safeStatusUpdate(statusUpdateHandler, 'Resolving old PDS') 82 + usersDid = await handleResolver.resolve(oldHandle) 83 + const didDoc = await docResolver.resolve(usersDid) 84 + safeStatusUpdate( 85 + statusUpdateHandler, 86 + 'Resolving did document and finding your current PDS URL', 87 + ) 88 89 + let oldPds 90 + try { 91 + oldPds = didDoc.service.filter(s => s.type === 'AtprotoPersonalDataServer')[0] 92 + .serviceEndpoint 93 + } catch (error) { 94 + console.error(error) 95 + throw new Error('Could not find a PDS in the DID document.') 96 + } 97 98 + oldAgent = new AtpAgent({ 99 + service: oldPds, 100 + }) 101 + } 102 103 + safeStatusUpdate(statusUpdateHandler, 'Logging you in to the old PDS') 104 + //Login to the old PDS 105 + if (twoFactorCode === null) { 106 + await oldAgent.login({ identifier: oldHandle, password }) 107 + } else { 108 + await oldAgent.login({ 109 + identifier: oldHandle, 110 + password: password, 111 + authFactorToken: twoFactorCode, 112 + }) 113 + } 114 115 + safeStatusUpdate( 116 + statusUpdateHandler, 117 + 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)', 118 + ) 119 + const newAgent = new AtpAgent({ service: newPdsUrl }) 120 + const newHostDesc = await newAgent.com.atproto.server.describeServer() 121 + if (this.createNewAccount) { 122 + const newHostWebDid = newHostDesc.data.did 123 124 + safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS') 125 126 + const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ 127 + aud: newHostWebDid, 128 + lxm: 'com.atproto.server.createAccount', 129 + }) 130 + const serviceJwt = createAuthResp.data.token 131 132 + let createAccountRequest = { 133 + did: usersDid, 134 + handle: newHandle, 135 + email: newEmail, 136 + password: password, 137 + } 138 + if (inviteCode) { 139 + createAccountRequest.inviteCode = inviteCode 140 + } 141 + if (verificationCode) { 142 + createAccountRequest.verificationCode = verificationCode 143 + } 144 + const createNewAccount = await newAgent.com.atproto.server.createAccount( 145 + createAccountRequest, 146 + { 147 + headers: { authorization: `Bearer ${serviceJwt}` }, 148 + encoding: 'application/json', 149 + }, 150 + ) 151 152 + if (createNewAccount.data.did !== usersDid.toString()) { 153 + throw new Error('Did not create the new account with the same did as the old account') 154 + } 155 + } 156 + safeStatusUpdate(statusUpdateHandler, 'Logging in with the new account') 157 158 + await newAgent.login({ 159 + identifier: usersDid, 160 + password: password, 161 + }) 162 163 + if (this.migrateRepo) { 164 + safeStatusUpdate(statusUpdateHandler, 'Migrating your repo') 165 + const repoRes = await oldAgent.com.atproto.sync.getRepo({ 166 + did: usersDid, 167 + }) 168 + await newAgent.com.atproto.repo.importRepo(repoRes.data, { 169 + encoding: 'application/vnd.ipld.car', 170 + }) 171 + } 172 173 + let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus() 174 175 + if (this.migrateBlobs) { 176 + safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs') 177 178 + let blobCursor = undefined 179 + let uploadedBlobs = 0 180 + do { 181 + safeStatusUpdate( 182 + statusUpdateHandler, 183 + `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`, 184 + ) 185 186 + const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 187 + did: usersDid, 188 + cursor: blobCursor, 189 + limit: 100, 190 + }) 191 192 + for (const cid of listedBlobs.data.cids) { 193 + try { 194 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ 195 + did: usersDid, 196 + cid, 197 + }) 198 + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 199 + encoding: blobRes.headers['content-type'], 200 + }) 201 + uploadedBlobs++ 202 + if (uploadedBlobs % 10 === 0) { 203 + safeStatusUpdate( 204 + statusUpdateHandler, 205 + `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`, 206 + ) 207 + } 208 + } catch (error) { 209 + console.error(error) 210 + } 211 } 212 + blobCursor = listedBlobs.data.cursor 213 + } while (blobCursor) 214 + } 215 216 + if (this.migrateMissingBlobs) { 217 + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus() 218 + if (newAccountStatus.data.expectedBlobs !== newAccountStatus.data.importedBlobs) { 219 + let totalMissingBlobs = 220 + newAccountStatus.data.expectedBlobs - newAccountStatus.data.importedBlobs 221 + safeStatusUpdate( 222 + statusUpdateHandler, 223 + 'Looks like there are some missing blobs. Going to try and upload them now.', 224 + ) 225 + //Probably should be shared between main blob uploader, but eh 226 + let missingBlobCursor = undefined 227 + let missingUploadedBlobs = 0 228 + do { 229 + safeStatusUpdate( 230 + statusUpdateHandler, 231 + `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`, 232 + ) 233 234 + const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs({ 235 + cursor: missingBlobCursor, 236 + limit: 100, 237 + }) 238 239 + for (const recordBlob of missingBlobs.data.blobs) { 240 + try { 241 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ 242 + did: usersDid, 243 + cid: recordBlob.cid, 244 + }) 245 + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 246 + encoding: blobRes.headers['content-type'], 247 + }) 248 + if (missingUploadedBlobs % 10 === 0) { 249 + safeStatusUpdate( 250 + statusUpdateHandler, 251 + `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`, 252 + ) 253 + } 254 + missingUploadedBlobs++ 255 + } catch (error) { 256 + //TODO silently logging prob should list them so user can manually download 257 + console.error(error) 258 + this.missingBlobs.push(recordBlob.cid) 259 } 260 + } 261 + missingBlobCursor = missingBlobs.data.cursor 262 + } while (missingBlobCursor) 263 + } 264 + } 265 + if (this.migratePrefs) { 266 + const prefs = await oldAgent.app.bsky.actor.getPreferences() 267 + await newAgent.app.bsky.actor.putPreferences(prefs.data) 268 + } 269 270 + this.oldAgent = oldAgent 271 + this.newAgent = newAgent 272 273 + if (this.migratePlcRecord) { 274 + await oldAgent.com.atproto.identity.requestPlcOperationSignature() 275 + safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token') 276 } 277 + } 278 279 + /** 280 + * Sign and submits the PLC operation to officially migrate the account 281 + * @param {string} token - the PLC token sent in the email. If you're just wanting to run this rerun migrate with all the flags set as false except for migratePlcRecord 282 + * @param additionalRotationKeysToAdd {string[]} - additional rotation keys to add in addition to the ones provided by the new PDS. 283 + * @returns {Promise<void>} 284 + */ 285 + async signPlcOperation(token, additionalRotationKeysToAdd = []) { 286 + const getDidCredentials = 287 + await this.newAgent.com.atproto.identity.getRecommendedDidCredentials() 288 + const pdsProvidedRotationKeys = getDidCredentials.data.rotationKeys ?? [] 289 + // Prepend any additional rotation keys (e.g., user-added keys, newly created key) so they appear above the new PDS rotation key 290 + const rotationKeys = [...(additionalRotationKeysToAdd || []), ...pdsProvidedRotationKeys] 291 + if (!rotationKeys) { 292 + throw new Error('No rotation key provided from the new PDS') 293 + } 294 + const credentials = { 295 + ...getDidCredentials.data, 296 + rotationKeys: rotationKeys, 297 + } 298 299 + const plcOp = await this.oldAgent.com.atproto.identity.signPlcOperation({ 300 + token: token, 301 + ...credentials, 302 + }) 303 304 + await this.newAgent.com.atproto.identity.submitPlcOperation({ 305 + operation: plcOp.data.operation, 306 + }) 307 308 + await this.newAgent.com.atproto.server.activateAccount() 309 + await this.oldAgent.com.atproto.server.deactivateAccount({}) 310 + } 311 312 + /** 313 + * Using this method assumes the Migrator class was constructed new and this was called. 314 + * Find the user's previous PDS from the PLC op logs, 315 + * logs in and deactivates their old account if it was found still active. 316 + * 317 + * @param oldHandle {string} 318 + * @param oldPassword {string} 319 + * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. 320 + * Like (status) => console.log(status) 321 + * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 322 + * @returns {Promise<void>} 323 + */ 324 + async deactivateOldAccount( 325 + oldHandle, 326 + oldPassword, 327 + statusUpdateHandler = null, 328 + twoFactorCode = null, 329 + ) { 330 + //Leaving this logic that either sets the agent to bsky.social, or the PDS since it's what I found worked best for migrations. 331 + // handleAndPDSResolver should be able to handle it, but there have been edge cases and this was what worked best oldHandle = cleanHandle(oldHandle); 332 + let usersDid 333 + //If it's a bsky handle just go with the entryway and let it sort everything 334 + if (oldHandle.endsWith('.bsky.social')) { 335 + const publicAgent = new AtpAgent({ 336 + service: 'https://public.api.bsky.app', 337 + }) 338 + const resolveIdentityFromEntryway = await publicAgent.com.atproto.identity.resolveHandle({ 339 + handle: oldHandle, 340 + }) 341 + usersDid = resolveIdentityFromEntryway.data.did 342 + } else { 343 + //Resolves the did and finds the did document for the old PDS 344 + safeStatusUpdate(statusUpdateHandler, 'Resolving did from handle') 345 + usersDid = await handleResolver.resolve(oldHandle) 346 } 347 348 + const didDoc = await docResolver.resolve(usersDid) 349 + let currentPds 350 + try { 351 + currentPds = didDoc.service.filter(s => s.type === 'AtprotoPersonalDataServer')[0] 352 + .serviceEndpoint 353 + } catch (error) { 354 + console.error(error) 355 + throw new Error('Could not find a PDS in the DID document.') 356 + } 357 358 + const plcLogRequest = await fetch(`https://plc.directory/${usersDid}/log`) 359 + const plcLog = await plcLogRequest.json() 360 + let pdsBeforeCurrent = '' 361 + for (const log of plcLog) { 362 + try { 363 + const pds = log.services.atproto_pds.endpoint 364 + if (pds.toLowerCase() === currentPds.toLowerCase()) { 365 + console.log('Found the PDS before the current one') 366 + break 367 } 368 + pdsBeforeCurrent = pds 369 + } catch (e) { 370 + console.log(e) 371 + } 372 + } 373 + if (pdsBeforeCurrent === '') { 374 + throw new Error('Could not find the PDS before the current one') 375 + } 376 377 + let oldAgent = new AtpAgent({ service: pdsBeforeCurrent }) 378 + safeStatusUpdate(statusUpdateHandler, `Logging you in to the old PDS: ${pdsBeforeCurrent}`) 379 + //Login to the old PDS 380 + if (twoFactorCode === null) { 381 + await oldAgent.login({ identifier: oldHandle, password: oldPassword }) 382 + } else { 383 + await oldAgent.login({ 384 + identifier: oldHandle, 385 + password: oldPassword, 386 + authFactorToken: twoFactorCode, 387 + }) 388 + } 389 + safeStatusUpdate(statusUpdateHandler, "Checking this isn't your current PDS") 390 + if (pdsBeforeCurrent === currentPds) { 391 + throw new Error('This is your current PDS. Login to your old account username and password') 392 + } 393 394 + let currentAccountStatus = await oldAgent.com.atproto.server.checkAccountStatus() 395 + if (!currentAccountStatus.data.activated) { 396 + safeStatusUpdate(statusUpdateHandler, 'All good. Your old account is not activated.') 397 } 398 + safeStatusUpdate(statusUpdateHandler, 'Deactivating your OLD account') 399 + await oldAgent.com.atproto.server.deactivateAccount({}) 400 + safeStatusUpdate(statusUpdateHandler, 'Successfully deactivated your OLD account') 401 + } 402 403 + /** 404 + * Signs the logged-in user in this.newAgent for backups with PDS MOOver. This is usually called after migrate and signPlcOperation are successful 405 + * 406 + * @param {string} didWeb 407 + * @returns {Promise<void>} 408 + */ 409 + async signUpForBackupsFromMigration(didWeb = 'did:web:pdsmoover.com') { 410 + //Manually grabbing the jwt and making a call with fetch cause for the life of me I could not figure out 411 + //how you used @atproto/api to make a call for proxying 412 + const url = `${this.newAgent.serviceUrl.origin}/xrpc/com.pdsmoover.backup.signUp` 413 414 + const accessJwt = this.newAgent?.session?.accessJwt 415 + if (!accessJwt) { 416 + throw new Error('Missing access token for authorization') 417 + } 418 419 + const res = await fetch(url, { 420 + method: 'POST', 421 + headers: { 422 + 'Authorization': `Bearer ${accessJwt}`, 423 + 'Content-Type': 'application/json', 424 + 'Accept': 'application/json', 425 + 'atproto-proxy': `${didWeb}#repo_backup`, 426 + }, 427 + body: JSON.stringify({}), 428 + }) 429 430 + if (!res.ok) { 431 + let bodyText = '' 432 + try { 433 + bodyText = await res.text() 434 + } catch {} 435 + throw new Error( 436 + `Backup signup failed: ${res.status} ${res.statusText}${bodyText ? ` - ${bodyText}` : ''}`, 437 + ) 438 + } 439 440 + //No return the success is all that is needed, if there's an error it will throw 441 + } 442 } 443 444 + export { Migrator }
+242 -239
packages/moover/lib/plc-ops.js
··· 8 * @typedef {import('@atcute/did-plc').IndexedEntry} IndexedEntry 9 */ 10 11 - import {defs, normalizeOp} from '@atcute/did-plc'; 12 - import {P256PrivateKey, parsePrivateMultikey, Secp256k1PrivateKey, Secp256k1PrivateKeyExportable} from '@atcute/crypto'; 13 - import * as CBOR from '@atcute/cbor'; 14 - import {fromBase16, toBase64Url} from '@atcute/multibase'; 15 - 16 17 // Helper to base64url-encode JSON 18 - const jsonToB64Url = (obj) => { 19 - const enc = new TextEncoder(); 20 - const json = JSON.stringify(obj); 21 - return toBase64Url(enc.encode(json)); 22 - }; 23 24 /** 25 * Class to help with various PLC operations 26 */ 27 class PlcOps { 28 /** 29 - * 30 - * @param plcDirectoryUrl {string} - The url of the plc directory, defaults to https://plc.directory 31 */ 32 - constructor(plcDirectoryUrl = 'https://plc.directory') { 33 - /** 34 - * The url of the plc directory 35 - * @type {string} 36 - */ 37 - this.plcDirectoryUrl = plcDirectoryUrl; 38 - } 39 40 - /** 41 - * Gets the current rotation keys for a user via their last PlC operation 42 - * @param did 43 - * @returns {Promise<string[]>} 44 - */ 45 - async getCurrentRotationKeysForUser(did) { 46 - const logs = await this.getPlcAuditLogs(did); 47 - const {lastOperation} = this.getLastPlcOp(logs); 48 - return lastOperation.rotationKeys || []; 49 - } 50 51 - /** 52 - * Gets the last PlC operation for a user from the plc directory 53 - * @param did 54 - * @returns {Promise<{lastOperation: Operation, base: any}>} 55 - */ 56 - async getLastPlcOpFromPlc(did) { 57 - const logs = await this.getPlcAuditLogs(did); 58 - return this.getLastPlcOp(logs); 59 - } 60 61 - /** 62 - * 63 - * @param logs {IndexedEntryLog} 64 - * @returns {{lastOperation: Operation, base: IndexedEntry}} 65 - */ 66 - getLastPlcOp(logs) { 67 - const lastOp = logs.at(-1); 68 - return {lastOperation: normalizeOp(lastOp.operation), base: lastOp}; 69 } 70 71 72 - /** 73 - * Gets the plc audit logs for a user from the plc directory 74 - * @param did 75 - * @returns {Promise<IndexedEntryLog>} 76 - */ 77 - async getPlcAuditLogs(did) { 78 - const response = await fetch(`${this.plcDirectoryUrl}/${did}/log/audit`); 79 - if (!response.ok) { 80 - throw new Error(`got response ${response.status}`); 81 - } 82 - 83 - const json = await response.json(); 84 - return defs.indexedEntryLog.parse(json); 85 } 86 87 - /** 88 - * Creates a new secp256k1 key that can be used for either rotation or verification key 89 - * @returns {Promise<{privateKey: string, publicKey: `did:key:${string}`}>} 90 - */ 91 - async createANewSecp256k1() { 92 - let keypair = await Secp256k1PrivateKeyExportable.createKeypair(); 93 - let publicKey = await keypair.exportPublicKey('did'); 94 - let privateKey = await keypair.exportPrivateKey('multikey'); 95 - return { 96 - privateKey, 97 - publicKey 98 - }; 99 } 100 101 - 102 - /** 103 - * Signs a new operation with the provided signing key, and information and submits it to the plc directory 104 - * @param did {string} - The user's did 105 - * @param signingRotationKey { P256PrivateKey|Secp256k1PrivateKey} - The keypair to sign the op with 106 - * @param alsoKnownAs {string[]} 107 - * @param rotationKeys {string[]} 108 - * @param pds {string} 109 - * @param verificationKey {string} - The public verification key 110 - * @param prev {string} - The previous valid operation's cid. 111 - * @returns {Promise<void>} 112 - */ 113 - async signAndPublishNewOp(did, signingRotationKey, alsoKnownAs, rotationKeys, pds, verificationKey, prev) { 114 - 115 - const rotationKeysToUse = [...new Set(rotationKeys)]; 116 - if (!rotationKeysToUse) { 117 - throw new Error('No rotation keys were found to be added to the PLC'); 118 - } 119 - 120 - if (rotationKeysToUse.length > 5) { 121 - throw new Error('You can only add up to 5 rotation keys to the PLC'); 122 - } 123 - 124 - const operation = { 125 - type: 'plc_operation', 126 - prev, 127 - alsoKnownAs, 128 - rotationKeys: rotationKeysToUse, 129 - services: { 130 - atproto_pds: { 131 - type: 'AtprotoPersonalDataServer', 132 - endpoint: pds 133 - } 134 - }, 135 - verificationMethods: { 136 - atproto: verificationKey 137 - } 138 - }; 139 - const opBytes = CBOR.encode(operation); 140 - const sigBytes = await signingRotationKey.sign(opBytes); 141 142 - const signature = toBase64Url(sigBytes); 143 144 - const signedOperation = { 145 - ...operation, 146 - sig: signature, 147 - }; 148 149 - await this.pushPlcOperation(did, signedOperation); 150 } 151 152 - /** 153 - * Takes a multi or hex based private key and returns a keypair 154 - * @param privateKeyString {string} 155 - * @param type {string} - secp256k1 or p256, needed if the private key is hex based, can be assumed if it's a multikey 156 - * @returns {Promise<{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}>} 157 - */ 158 - async getKeyPair(privateKeyString, type = 'secp256k1') { 159 - const HEX_REGEX = /^[0-9a-f]+$/i; 160 - const MULTIKEY_REGEX = /^z[a-km-zA-HJ-NP-Z1-9]+$/; 161 - let keypair = undefined; 162 163 - if (HEX_REGEX.test(privateKeyString)) { 164 - const privateKeyBytes = fromBase16(privateKeyString); 165 166 - switch (type) { 167 - case 'p256': { 168 - keypair = await P256PrivateKey.importRaw(privateKeyBytes); 169 - break; 170 - } 171 - case 'secp256k1': { 172 - keypair = await Secp256k1PrivateKey.importRaw(privateKeyBytes); 173 - break; 174 - } 175 - default: { 176 - throw new Error(`unsupported "${type}" type`); 177 - } 178 - } 179 - } else if (MULTIKEY_REGEX.test(privateKeyString)) { 180 181 - const match = parsePrivateMultikey(privateKeyString); 182 - const privateKeyBytes = match.privateKeyBytes; 183 184 - switch (match.type) { 185 - case 'p256': { 186 - keypair = await P256PrivateKey.importRaw(privateKeyBytes); 187 - console.log(keypair); 188 - break; 189 - } 190 - case 'secp256k1': { 191 - keypair = await Secp256k1PrivateKey.importRaw(privateKeyBytes); 192 - break; 193 - } 194 - default: { 195 - throw new Error(`unsupported "${type}" type`); 196 - } 197 - } 198 - } else { 199 - throw new Error('unknown input format'); 200 } 201 - return { 202 - type: 'private_key', 203 - didPublicKey: await keypair.exportPublicKey('did'), 204 - keypair: keypair, 205 - }; 206 } 207 - 208 - /** 209 - * Submits a new operation to the plc directory 210 - * @param did {string} - The user's did 211 - * @param operation 212 - * @returns {Promise<void>} 213 - */ 214 - async pushPlcOperation(did, operation) { 215 - const response = await fetch(`${this.plcDirectoryUrl}/${did}`, { 216 - method: 'post', 217 - headers: { 218 - 'content-type': 'application/json', 219 - }, 220 - body: JSON.stringify(operation), 221 - }); 222 223 - const headers = response.headers; 224 - if (!response.ok) { 225 - const type = headers.get('content-type'); 226 227 - if (type?.includes('application/json')) { 228 - const json = await response.json(); 229 - if (typeof json === 'object' && json !== null && typeof json.message === 'string') { 230 - throw new Error(json.message); 231 - } 232 - } 233 234 - throw new Error(`got http ${response.status} from plc`); 235 } 236 - }; 237 - 238 - 239 - /** 240 - * Creates a new service auth token for a user. This is what is used to create a new account on a PDS for your did 241 - * 242 - * @param iss The user's did 243 - * @param aud The did:web, if it's a PDS it's usually from /xrpc/com.atproto.server.describeServer 244 - * @param keypair The keypair to sign with only supporting ES256K atm 245 - * @param lxm The lxm which is usually com.atproto.server.createAccount for creating a new account 246 - * @returns {Promise<string>} 247 - */ 248 - async createANewServiceAuthToken(iss, aud, keypair, lxm) { 249 - 250 - 251 - // Compute iat/exp defaults (60s window like reference: MINUTE/1e3) 252 - const iat = Math.floor(Date.now() / 1e3); 253 - const exp = iat + 60; 254 - 255 - // Generate a 16-byte hex jti 256 - const jti = (() => { 257 - const bytes = new Uint8Array(16); 258 - // crypto in browser or node; fall back safely 259 - (globalThis.crypto || window.crypto).getRandomValues(bytes); 260 - return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); 261 - })(); 262 263 264 - // Build header and payload (omit undefined fields) 265 - // Just defaulting to ES256K since p256 was not importing on firefox 266 - const header = {typ: 'JWT', alg: 'ES256K'}; 267 - const payload = {}; 268 - payload.iat = iat; 269 - payload.iss = iss; 270 - payload.aud = aud; 271 - payload.exp = exp; 272 - payload.lxm = lxm; 273 - payload.jti = jti; 274 275 - const headerB64 = jsonToB64Url(header); 276 - const payloadB64 = jsonToB64Url(payload); 277 - const toSignStr = `${headerB64}.${payloadB64}`; 278 279 - // Sign 280 - const toSignBytes = new TextEncoder().encode(toSignStr); 281 - const sigBytes = await keypair.sign(toSignBytes); 282 283 - // Return compact JWS 284 - const sigB64 = toBase64Url(sigBytes); 285 - return `${toSignStr}.${sigB64}`; 286 - } 287 288 289 } 290 291 - 292 - export {PlcOps};
··· 8 * @typedef {import('@atcute/did-plc').IndexedEntry} IndexedEntry 9 */ 10 11 + import { defs, normalizeOp } from '@atcute/did-plc' 12 + import { 13 + P256PrivateKey, 14 + parsePrivateMultikey, 15 + Secp256k1PrivateKey, 16 + Secp256k1PrivateKeyExportable, 17 + } from '@atcute/crypto' 18 + import * as CBOR from '@atcute/cbor' 19 + import { fromBase16, toBase64Url } from '@atcute/multibase' 20 21 // Helper to base64url-encode JSON 22 + const jsonToB64Url = obj => { 23 + const enc = new TextEncoder() 24 + const json = JSON.stringify(obj) 25 + return toBase64Url(enc.encode(json)) 26 + } 27 28 /** 29 * Class to help with various PLC operations 30 */ 31 class PlcOps { 32 + /** 33 + * 34 + * @param plcDirectoryUrl {string} - The url of the plc directory, defaults to https://plc.directory 35 + */ 36 + constructor(plcDirectoryUrl = 'https://plc.directory') { 37 /** 38 + * The url of the plc directory 39 + * @type {string} 40 */ 41 + this.plcDirectoryUrl = plcDirectoryUrl 42 + } 43 44 + /** 45 + * Gets the current rotation keys for a user via their last PlC operation 46 + * @param did 47 + * @returns {Promise<string[]>} 48 + */ 49 + async getCurrentRotationKeysForUser(did) { 50 + const logs = await this.getPlcAuditLogs(did) 51 + const { lastOperation } = this.getLastPlcOp(logs) 52 + return lastOperation.rotationKeys || [] 53 + } 54 + 55 + /** 56 + * Gets the last PlC operation for a user from the plc directory 57 + * @param did 58 + * @returns {Promise<{lastOperation: Operation, base: any}>} 59 + */ 60 + async getLastPlcOpFromPlc(did) { 61 + const logs = await this.getPlcAuditLogs(did) 62 + return this.getLastPlcOp(logs) 63 + } 64 65 + /** 66 + * 67 + * @param logs {IndexedEntryLog} 68 + * @returns {{lastOperation: Operation, base: IndexedEntry}} 69 + */ 70 + getLastPlcOp(logs) { 71 + const lastOp = logs.at(-1) 72 + return { lastOperation: normalizeOp(lastOp.operation), base: lastOp } 73 + } 74 75 + /** 76 + * Gets the plc audit logs for a user from the plc directory 77 + * @param did 78 + * @returns {Promise<IndexedEntryLog>} 79 + */ 80 + async getPlcAuditLogs(did) { 81 + const response = await fetch(`${this.plcDirectoryUrl}/${did}/log/audit`) 82 + if (!response.ok) { 83 + throw new Error(`got response ${response.status}`) 84 } 85 86 + const json = await response.json() 87 + return defs.indexedEntryLog.parse(json) 88 + } 89 90 + /** 91 + * Creates a new secp256k1 key that can be used for either rotation or verification key 92 + * @returns {Promise<{privateKey: string, publicKey: `did:key:${string}`}>} 93 + */ 94 + async createANewSecp256k1() { 95 + let keypair = await Secp256k1PrivateKeyExportable.createKeypair() 96 + let publicKey = await keypair.exportPublicKey('did') 97 + let privateKey = await keypair.exportPrivateKey('multikey') 98 + return { 99 + privateKey, 100 + publicKey, 101 } 102 + } 103 104 + /** 105 + * Signs a new operation with the provided signing key, and information and submits it to the plc directory 106 + * @param did {string} - The user's did 107 + * @param signingRotationKey { P256PrivateKey|Secp256k1PrivateKey} - The keypair to sign the op with 108 + * @param alsoKnownAs {string[]} 109 + * @param rotationKeys {string[]} 110 + * @param pds {string} 111 + * @param verificationKey {string} - The public verification key 112 + * @param prev {string} - The previous valid operation's cid. 113 + * @returns {Promise<void>} 114 + */ 115 + async signAndPublishNewOp( 116 + did, 117 + signingRotationKey, 118 + alsoKnownAs, 119 + rotationKeys, 120 + pds, 121 + verificationKey, 122 + prev, 123 + ) { 124 + const rotationKeysToUse = [...new Set(rotationKeys)] 125 + if (!rotationKeysToUse) { 126 + throw new Error('No rotation keys were found to be added to the PLC') 127 } 128 129 + if (rotationKeysToUse.length > 5) { 130 + throw new Error('You can only add up to 5 rotation keys to the PLC') 131 + } 132 133 + const operation = { 134 + type: 'plc_operation', 135 + prev, 136 + alsoKnownAs, 137 + rotationKeys: rotationKeysToUse, 138 + services: { 139 + atproto_pds: { 140 + type: 'AtprotoPersonalDataServer', 141 + endpoint: pds, 142 + }, 143 + }, 144 + verificationMethods: { 145 + atproto: verificationKey, 146 + }, 147 + } 148 + const opBytes = CBOR.encode(operation) 149 + const sigBytes = await signingRotationKey.sign(opBytes) 150 151 + const signature = toBase64Url(sigBytes) 152 153 + const signedOperation = { 154 + ...operation, 155 + sig: signature, 156 } 157 158 + await this.pushPlcOperation(did, signedOperation) 159 + } 160 161 + /** 162 + * Takes a multi or hex based private key and returns a keypair 163 + * @param privateKeyString {string} 164 + * @param type {string} - secp256k1 or p256, needed if the private key is hex based, can be assumed if it's a multikey 165 + * @returns {Promise<{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}>} 166 + */ 167 + async getKeyPair(privateKeyString, type = 'secp256k1') { 168 + const HEX_REGEX = /^[0-9a-f]+$/i 169 + const MULTIKEY_REGEX = /^z[a-km-zA-HJ-NP-Z1-9]+$/ 170 + let keypair = undefined 171 172 + if (HEX_REGEX.test(privateKeyString)) { 173 + const privateKeyBytes = fromBase16(privateKeyString) 174 175 + switch (type) { 176 + case 'p256': { 177 + keypair = await P256PrivateKey.importRaw(privateKeyBytes) 178 + break 179 + } 180 + case 'secp256k1': { 181 + keypair = await Secp256k1PrivateKey.importRaw(privateKeyBytes) 182 + break 183 + } 184 + default: { 185 + throw new Error(`unsupported "${type}" type`) 186 + } 187 + } 188 + } else if (MULTIKEY_REGEX.test(privateKeyString)) { 189 + const match = parsePrivateMultikey(privateKeyString) 190 + const privateKeyBytes = match.privateKeyBytes 191 192 + switch (match.type) { 193 + case 'p256': { 194 + keypair = await P256PrivateKey.importRaw(privateKeyBytes) 195 + console.log(keypair) 196 + break 197 } 198 + case 'secp256k1': { 199 + keypair = await Secp256k1PrivateKey.importRaw(privateKeyBytes) 200 + break 201 + } 202 + default: { 203 + throw new Error(`unsupported "${type}" type`) 204 + } 205 + } 206 + } else { 207 + throw new Error('unknown input format') 208 } 209 + return { 210 + type: 'private_key', 211 + didPublicKey: await keypair.exportPublicKey('did'), 212 + keypair: keypair, 213 + } 214 + } 215 216 + /** 217 + * Submits a new operation to the plc directory 218 + * @param did {string} - The user's did 219 + * @param operation 220 + * @returns {Promise<void>} 221 + */ 222 + async pushPlcOperation(did, operation) { 223 + const response = await fetch(`${this.plcDirectoryUrl}/${did}`, { 224 + method: 'post', 225 + headers: { 226 + 'content-type': 'application/json', 227 + }, 228 + body: JSON.stringify(operation), 229 + }) 230 231 + const headers = response.headers 232 + if (!response.ok) { 233 + const type = headers.get('content-type') 234 235 + if (type?.includes('application/json')) { 236 + const json = await response.json() 237 + if (typeof json === 'object' && json !== null && typeof json.message === 'string') { 238 + throw new Error(json.message) 239 } 240 + } 241 242 + throw new Error(`got http ${response.status} from plc`) 243 + } 244 + } 245 246 + /** 247 + * Creates a new service auth token for a user. This is what is used to create a new account on a PDS for your did 248 + * 249 + * @param iss The user's did 250 + * @param aud The did:web, if it's a PDS it's usually from /xrpc/com.atproto.server.describeServer 251 + * @param keypair The keypair to sign with only supporting ES256K atm 252 + * @param lxm The lxm which is usually com.atproto.server.createAccount for creating a new account 253 + * @returns {Promise<string>} 254 + */ 255 + async createANewServiceAuthToken(iss, aud, keypair, lxm) { 256 + // Compute iat/exp defaults (60s window like reference: MINUTE/1e3) 257 + const iat = Math.floor(Date.now() / 1e3) 258 + const exp = iat + 60 259 260 + // Generate a 16-byte hex jti 261 + const jti = (() => { 262 + const bytes = new Uint8Array(16) 263 + // crypto in browser or node; fall back safely 264 + ;(globalThis.crypto || window.crypto).getRandomValues(bytes) 265 + return Array.from(bytes) 266 + .map(b => b.toString(16).padStart(2, '0')) 267 + .join('') 268 + })() 269 270 + // Build header and payload (omit undefined fields) 271 + // Just defaulting to ES256K since p256 was not importing on firefox 272 + const header = { typ: 'JWT', alg: 'ES256K' } 273 + const payload = {} 274 + payload.iat = iat 275 + payload.iss = iss 276 + payload.aud = aud 277 + payload.exp = exp 278 + payload.lxm = lxm 279 + payload.jti = jti 280 281 + const headerB64 = jsonToB64Url(header) 282 + const payloadB64 = jsonToB64Url(payload) 283 + const toSignStr = `${headerB64}.${payloadB64}` 284 285 + // Sign 286 + const toSignBytes = new TextEncoder().encode(toSignStr) 287 + const sigBytes = await keypair.sign(toSignBytes) 288 289 + // Return compact JWS 290 + const sigB64 = toBase64Url(sigBytes) 291 + return `${toSignStr}.${sigB64}` 292 + } 293 } 294 295 + export { PlcOps }
+283 -281
packages/moover/lib/restore.js
··· 1 /** 2 * @typedef {import('@atcute/did-plc').Operation} Operation 3 */ 4 - import {P256PrivateKey, Secp256k1PrivateKey} from '@atcute/crypto'; 5 - import {handleAndPDSResolver} from './atprotoUtils.js'; 6 - import {PlcOps} from './plc-ops.js'; 7 - import {normalizeOp} from '@atcute/did-plc'; 8 - import {AtpAgent} from '@atproto/api'; 9 - import {Secp256k1PrivateKeyExportable} from '@atcute/crypto'; 10 - import * as CBOR from '@atcute/cbor'; 11 - import {toBase64Url} from '@atcute/multibase'; 12 13 class Restore { 14 15 /** 16 - * 17 - * @param pdsMooverInstance {string} - The url of the pds moover instance to restore from. Defaults to https://pdsmover.com 18 */ 19 - constructor(pdsMooverInstance = 'https://pdsmover.com') { 20 - /** 21 - * If you want to use a different plc directory create your own instance of the plc ops class and pass it in here 22 - * @type {PlcOps} */ 23 - this.plcOps = new PlcOps(); 24 - 25 - /** 26 - * This is the base url for the pds moover instance used to restore the files from a backup. 27 - * @type {string} 28 - */ 29 - this.pdsMooverInstance = pdsMooverInstance 30 - 31 - /** 32 - * To keep it simple, only uses secp256k for the temp verification key that is used to create the new account on the new PDS 33 - * and is temporarily assigned to the user's account on PLC 34 - * @type {null|Secp256k1PrivateKeyExportable} 35 - */ 36 - this.tempVerificationKeypair = null; 37 - 38 - /** @type {AtpAgent} */ 39 - this.atpAgent = null; 40 - 41 - /** 42 - * The keypair that is used to sign the plc operation 43 - * @type {null|{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}} 44 - */ 45 - this.recoveryRotationKeyPair = null; 46 - 47 - /** 48 - * If this is true we are just restoring the repo and blobs. Ideally for rerunning a restore process after account recovery 49 - * @type {boolean} 50 - */ 51 - this.RestoreFromBackup = true; 52 - 53 - /** 54 - * If set to true then it will do the account recovery. Writes a temp key to the did doc, 55 - * create a new account on the new pds, and then submit a new plc op for the pds to have control (finishes the migration, can always restore the backup later) 56 - * @type {boolean} 57 - */ 58 - this.AccountRecovery = true; 59 - } 60 61 /** 62 - * Recovers an account with the users rotation key and restores the repo from a PDS MOOver backup 63 - * This method can fail, and the account was still recovered, it's best to check the PLC logs to see where an account stands before reruns 64 - * @param rotationKey {string} - The users private rotation key, can be a multi key or hex key 65 - * @param rotationKeyType {string} - The type of the key, secp256k1 or p256. Required if the key is in hex format, defaults to secp256k1 66 - * @param currentHandleOrDid {string} - The users current handle or did, if they don't have a DNS record it will have to be their did for success 67 - * @param newPDS {string} - The new PDS url, like https://coolnewpds.com 68 - * @param newHandle {string} - Can be the users DNS handle if it is already setup with their did, if not it's bob.mypds.com 69 - * @param newPassword {string} - The new password for the new account 70 - * @param newEmail {string} - The new email for the new account 71 - * @param inviteCode {string|null} - The invite code for the new PDS if it requires one 72 - * @param cidToRestoreTo {string|null} - The cid of the plc op to restore to, used mostly to revert a fraudulent plc op. Want to give it the last valid operations cid 73 - * @param onStatus {function|null} - A function that takes a string used to update the UI. Like (status) => console.log(status) 74 - * @returns {Promise<void>} If there is a failure during restoring the back up (after the status Success! Restoring your repo...) then your account is most likely 75 - * recovered and future runs need to have the RestoreFromBackup flag set to true and AccountRecovery set to false. 76 */ 77 - async recover( 78 - rotationKey, 79 - rotationKeyType = 'secp256k1', 80 - currentHandleOrDid, 81 - newPDS, 82 - newHandle, 83 - newPassword, 84 - newEmail, 85 - inviteCode, 86 - cidToRestoreTo = null, 87 - onStatus = null) { 88 - 89 - if (onStatus) onStatus('Resolving your handle...'); 90 - 91 - let {usersDid} = await handleAndPDSResolver(currentHandleOrDid); 92 - 93 - if (onStatus) onStatus('Checking that the new PDS is an actual PDS (if the url is wrong, this takes a while to error out)'); 94 - this.atpAgent = new AtpAgent({service: newPDS}); 95 - const newHostDesc = await this.atpAgent.com.atproto.server.describeServer(); 96 - 97 - 98 - //Check to see if the user already has a repo on the new PDS, if they do no reason to try and restore via the plc operations 99 - try { 100 - await this.atpAgent.com.atproto.repo.describeRepo({repo: usersDid.toString()}); 101 - //If we got this far and there is a repo on the new PDS with the users did, we can just move on and restore the files. 102 - //We do not want to mess with the plc ops if we dont have to 103 - this.AccountRecovery = false; 104 105 - } catch (error) { 106 - console.error(error); 107 - let parsedError = error.error; 108 - if (parsedError === 'RepoDeactivated') { 109 - //Ideally should mean they already have a repo on the new PDS and we just need to restore the files 110 - this.AccountRecovery = false; 111 - } 112 - //This is the error we want to see, anything else throw 113 - if (parsedError !== 'RepoNotFound') { 114 - throw error; 115 - } 116 - } 117 - 118 - //We need to double check that the new handle has not been taken, if it has we need to throw an error 119 - //We care a bit more because we do not want any unnecessary plc ops to be created 120 - try { 121 - let resolveHandle = await this.atpAgent.com.atproto.identity.resolveHandle({handle: newHandle}); 122 - if (resolveHandle.data.did === usersDid.toString()) { 123 - //This was originally setting the AccountRecovery to false, which works if it is resolved via .well-known, but not dns 124 - //The idea was to check and see if the handle has been taken. just leaving for now since it does that check and if the user owns the handle 125 - //their did should be set anyhow 126 - 127 - } else { 128 - //There is a repo with that name and it's not the users did, 129 - throw new Error('The new handle is already taken, please select a different handle'); 130 - } 131 - } catch (error) { 132 - // Going to silently log this and just assume the handle has not been taken. 133 - console.error(error); 134 - if (error.message.startsWith('The new handle')) { 135 - //it's not our custom error, so we can just throw it 136 - throw error; 137 - } 138 139 - } 140 141 - if (this.AccountRecovery) { 142 143 - if (onStatus) onStatus('Validating your private rotation key is in the correct format...'); 144 145 - this.recoveryRotationKeyPair = await this.plcOps.getKeyPair(rotationKey, rotationKeyType); 146 147 148 - if (onStatus) onStatus('Resolving PlC operation logs...'); 149 150 - /** @type {Operation} */ 151 - let baseOpForSigning = null; 152 - let opPrevCid = null; 153 154 - //This is for reversals against a rogue plc op and you want to restore to a specific cid in the audit log 155 - if (cidToRestoreTo) { 156 - let auditLogs = await this.plcOps.getPlcAuditLogs(usersDid); 157 - for (const log of auditLogs) { 158 - if (log.cid === cidToRestoreTo) { 159 - baseOpForSigning = normalizeOp(log.operation); 160 - opPrevCid = log.cid; 161 - break; 162 - } 163 - } 164 - if (!baseOpForSigning) { 165 - throw new Error('Could not find the cid in the audit logs'); 166 - } 167 - } else { 168 - let {lastOperation, base} = await this.plcOps.getLastPlcOpFromPlc(usersDid); 169 - opPrevCid = base.cid; 170 - baseOpForSigning = lastOperation; 171 - } 172 173 - if (onStatus) onStatus('Preparing to switch to a temp atproto key...'); 174 - if (this.tempVerificationKeypair == null) { 175 - if (onStatus) onStatus('Creating a new temp atproto key...'); 176 - this.tempVerificationKeypair = await Secp256k1PrivateKeyExportable.createKeypair(); 177 - } 178 - //Just defaulting to the user's recovery key for now. Advance cases will be something else 179 - //Maybe just a new ui to edit the PLC doc in a limited capacity, but sinc ethis is a temp plc op i don't think it's needed 180 - let tempRotationKeys = [this.recoveryRotationKeyPair.didPublicKey]; 181 182 - if (onStatus) onStatus('Modifying the PLC OP for recovery...'); 183 - //A temp plc op for control of the atproto key to create a serviceAuth and new account on the new PDS 184 - await this.plcOps.signAndPublishNewOp( 185 - usersDid, 186 - this.recoveryRotationKeyPair.keypair, 187 - baseOpForSigning.alsoKnownAs, 188 - tempRotationKeys, 189 - newPDS, 190 - await this.tempVerificationKeypair.exportPublicKey('did'), 191 - opPrevCid); 192 193 194 - if (onStatus) onStatus('Creating your new account on the new PDS...'); 195 - let serviceAuthToken = await this.plcOps.createANewServiceAuthToken(usersDid, newHostDesc.data.did, this.tempVerificationKeypair, 'com.atproto.server.createAccount'); 196 197 - let createAccountRequest = { 198 - did: usersDid, 199 - handle: newHandle, 200 - email: newEmail, 201 - password: newPassword, 202 - }; 203 - if (inviteCode) { 204 - createAccountRequest.inviteCode = inviteCode; 205 - } 206 - const _ = await this.atpAgent.com.atproto.server.createAccount( 207 - createAccountRequest, 208 - { 209 - headers: {authorization: `Bearer ${serviceAuthToken}`}, 210 - encoding: 'application/json', 211 - }); 212 } 213 - 214 - await this.atpAgent.login({ 215 - identifier: usersDid, 216 - password: newPassword, 217 - }); 218 - 219 - if (this.AccountRecovery) { 220 - //Moving the user offically to the new PDS 221 - if (onStatus) onStatus('Signing the papers...'); 222 - let {base} = await this.plcOps.getLastPlcOpFromPlc(usersDid); 223 - await this.signRestorePlcOperation(usersDid, [this.recoveryRotationKeyPair.didPublicKey], base.cid); 224 } 225 226 - if (this.RestoreFromBackup) { 227 - if (onStatus) onStatus('Success! Restoring your repo...'); 228 - const pdsMoover = new AtpAgent({service: this.pdsMooverInstance}); 229 - const repoRes = await pdsMoover.com.atproto.sync.getRepo({did: usersDid}); 230 - await this.atpAgent.com.atproto.repo.importRepo(repoRes.data, { 231 - encoding: 'application/vnd.ipld.car', 232 - }); 233 234 - if (onStatus) onStatus('Restoring your blobs...'); 235 236 - //Using the missing endpoint to findout what's missing then the PDS MOOver endpoint to restore 237 - let totalMissingBlobs = 0; 238 - let missingBlobCursor = undefined; 239 - let missingUploadedBlobs = 0; 240 241 - do { 242 - 243 - const missingBlobs = await this.atpAgent.com.atproto.repo.listMissingBlobs({ 244 - cursor: missingBlobCursor, 245 - limit: 1000, 246 - }); 247 - totalMissingBlobs += missingBlobs.data.blobs.length; 248 - 249 - for (const recordBlob of missingBlobs.data.blobs) { 250 - try { 251 252 - const blobRes = await pdsMoover.com.atproto.sync.getBlob({ 253 - did: usersDid, 254 - cid: recordBlob.cid, 255 - }); 256 - let result = await this.atpAgent.com.atproto.repo.uploadBlob(blobRes.data, { 257 - encoding: blobRes.headers['content-type'], 258 - }); 259 260 261 - if (missingUploadedBlobs % 2 === 0) { 262 - if (onStatus) onStatus(`Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs} (The total may increase as we find more)`); 263 - } 264 - missingUploadedBlobs++; 265 - } catch (error) { 266 - console.error(error); 267 - } 268 - } 269 - missingBlobCursor = missingBlobs.data.cursor; 270 - } while (missingBlobCursor); 271 - } 272 - const accountStatus = await this.atpAgent.com.atproto.server.checkAccountStatus(); 273 - if (!accountStatus.data.activated) { 274 - if (onStatus) onStatus('Activating your account...'); 275 - await this.atpAgent.com.atproto.server.activateAccount(); 276 - } 277 278 - } 279 280 281 - /** 282 - * This method signs the plc operation over to the new PDS and activates the account 283 - * Assumes you have already created a new account during the recovery process and logged in 284 - * Uses the recommended did doc from the PDS as a base and adds the users rotation key to the rotation keys array 285 - * 286 - * @param usersDid 287 - * @param additionalRotationKeysToAdd 288 - * @param prevCid 289 - * @returns {Promise<void>} 290 - */ 291 - async signRestorePlcOperation(usersDid, additionalRotationKeysToAdd = [], prevCid) { 292 - const getDidCredentials = 293 - await this.atpAgent.com.atproto.identity.getRecommendedDidCredentials(); 294 295 - const pdsProvidedRotationKeys = getDidCredentials.data.rotationKeys ?? []; 296 - //Puts the provided rotation keys above the pds pro 297 - const rotationKeys = [...new Set([...(additionalRotationKeysToAdd || []), ...pdsProvidedRotationKeys])]; 298 - if (!rotationKeys) { 299 - throw new Error('No rotation keys were found to be added to the PLC'); 300 - } 301 302 - if (rotationKeys.length > 5) { 303 - throw new Error('You can only add up to 5 rotation keys to the PLC'); 304 } 305 306 307 - const plcOpToSubmit = { 308 - type: 'plc_operation', 309 - ...getDidCredentials.data, 310 - prev: prevCid, 311 - rotationKeys: rotationKeys, 312 - }; 313 - 314 315 - const opBytes = CBOR.encode(plcOpToSubmit); 316 - const sigBytes = await this.recoveryRotationKeyPair.keypair.sign(opBytes); 317 318 - const signature = toBase64Url(sigBytes); 319 320 - const signedOperation = { 321 - ...plcOpToSubmit, 322 - sig: signature, 323 - }; 324 325 - await this.plcOps.pushPlcOperation(usersDid, signedOperation); 326 - await this.atpAgent.com.atproto.server.activateAccount(); 327 328 } 329 } 330 331 - export {Restore};
··· 1 /** 2 * @typedef {import('@atcute/did-plc').Operation} Operation 3 */ 4 + import { P256PrivateKey, Secp256k1PrivateKey } from '@atcute/crypto' 5 + import { handleAndPDSResolver } from './atprotoUtils.js' 6 + import { PlcOps } from './plc-ops.js' 7 + import { normalizeOp } from '@atcute/did-plc' 8 + import { AtpAgent } from '@atproto/api' 9 + import { Secp256k1PrivateKeyExportable } from '@atcute/crypto' 10 + import * as CBOR from '@atcute/cbor' 11 + import { toBase64Url } from '@atcute/multibase' 12 13 class Restore { 14 + /** 15 + * 16 + * @param pdsMooverInstance {string} - The url of the pds moover instance to restore from. Defaults to https://pdsmover.com 17 + */ 18 + constructor(pdsMooverInstance = 'https://pdsmover.com') { 19 + /** 20 + * If you want to use a different plc directory create your own instance of the plc ops class and pass it in here 21 + * @type {PlcOps} */ 22 + this.plcOps = new PlcOps() 23 24 /** 25 + * This is the base url for the pds moover instance used to restore the files from a backup. 26 + * @type {string} 27 */ 28 + this.pdsMooverInstance = pdsMooverInstance 29 30 /** 31 + * To keep it simple, only uses secp256k for the temp verification key that is used to create the new account on the new PDS 32 + * and is temporarily assigned to the user's account on PLC 33 + * @type {null|Secp256k1PrivateKeyExportable} 34 */ 35 + this.tempVerificationKeypair = null 36 37 + /** @type {AtpAgent} */ 38 + this.atpAgent = null 39 40 + /** 41 + * The keypair that is used to sign the plc operation 42 + * @type {null|{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}} 43 + */ 44 + this.recoveryRotationKeyPair = null 45 46 + /** 47 + * If this is true we are just restoring the repo and blobs. Ideally for rerunning a restore process after account recovery 48 + * @type {boolean} 49 + */ 50 + this.RestoreFromBackup = true 51 52 + /** 53 + * If set to true then it will do the account recovery. Writes a temp key to the did doc, 54 + * create a new account on the new pds, and then submit a new plc op for the pds to have control (finishes the migration, can always restore the backup later) 55 + * @type {boolean} 56 + */ 57 + this.AccountRecovery = true 58 + } 59 60 + /** 61 + * Recovers an account with the users rotation key and restores the repo from a PDS MOOver backup 62 + * This method can fail, and the account was still recovered, it's best to check the PLC logs to see where an account stands before reruns 63 + * @param rotationKey {string} - The users private rotation key, can be a multi key or hex key 64 + * @param rotationKeyType {string} - The type of the key, secp256k1 or p256. Required if the key is in hex format, defaults to secp256k1 65 + * @param currentHandleOrDid {string} - The users current handle or did, if they don't have a DNS record it will have to be their did for success 66 + * @param newPDS {string} - The new PDS url, like https://coolnewpds.com 67 + * @param newHandle {string} - Can be the users DNS handle if it is already setup with their did, if not it's bob.mypds.com 68 + * @param newPassword {string} - The new password for the new account 69 + * @param newEmail {string} - The new email for the new account 70 + * @param inviteCode {string|null} - The invite code for the new PDS if it requires one 71 + * @param cidToRestoreTo {string|null} - The cid of the plc op to restore to, used mostly to revert a fraudulent plc op. Want to give it the last valid operations cid 72 + * @param onStatus {function|null} - A function that takes a string used to update the UI. Like (status) => console.log(status) 73 + * @returns {Promise<void>} If there is a failure during restoring the back up (after the status Success! Restoring your repo...) then your account is most likely 74 + * recovered and future runs need to have the RestoreFromBackup flag set to true and AccountRecovery set to false. 75 + */ 76 + async recover( 77 + rotationKey, 78 + rotationKeyType = 'secp256k1', 79 + currentHandleOrDid, 80 + newPDS, 81 + newHandle, 82 + newPassword, 83 + newEmail, 84 + inviteCode, 85 + cidToRestoreTo = null, 86 + onStatus = null, 87 + ) { 88 + if (onStatus) onStatus('Resolving your handle...') 89 90 + let { usersDid } = await handleAndPDSResolver(currentHandleOrDid) 91 92 + if (onStatus) 93 + onStatus( 94 + 'Checking that the new PDS is an actual PDS (if the url is wrong, this takes a while to error out)', 95 + ) 96 + this.atpAgent = new AtpAgent({ service: newPDS }) 97 + const newHostDesc = await this.atpAgent.com.atproto.server.describeServer() 98 99 + //Check to see if the user already has a repo on the new PDS, if they do no reason to try and restore via the plc operations 100 + try { 101 + await this.atpAgent.com.atproto.repo.describeRepo({ repo: usersDid.toString() }) 102 + //If we got this far and there is a repo on the new PDS with the users did, we can just move on and restore the files. 103 + //We do not want to mess with the plc ops if we dont have to 104 + this.AccountRecovery = false 105 + } catch (error) { 106 + console.error(error) 107 + let parsedError = error.error 108 + if (parsedError === 'RepoDeactivated') { 109 + //Ideally should mean they already have a repo on the new PDS and we just need to restore the files 110 + this.AccountRecovery = false 111 + } 112 + //This is the error we want to see, anything else throw 113 + if (parsedError !== 'RepoNotFound') { 114 + throw error 115 + } 116 + } 117 118 + //We need to double check that the new handle has not been taken, if it has we need to throw an error 119 + //We care a bit more because we do not want any unnecessary plc ops to be created 120 + try { 121 + let resolveHandle = await this.atpAgent.com.atproto.identity.resolveHandle({ 122 + handle: newHandle, 123 + }) 124 + if (resolveHandle.data.did === usersDid.toString()) { 125 + //This was originally setting the AccountRecovery to false, which works if it is resolved via .well-known, but not dns 126 + //The idea was to check and see if the handle has been taken. just leaving for now since it does that check and if the user owns the handle 127 + //their did should be set anyhow 128 + } else { 129 + //There is a repo with that name and it's not the users did, 130 + throw new Error('The new handle is already taken, please select a different handle') 131 + } 132 + } catch (error) { 133 + // Going to silently log this and just assume the handle has not been taken. 134 + console.error(error) 135 + if (error.message.startsWith('The new handle')) { 136 + //it's not our custom error, so we can just throw it 137 + throw error 138 + } 139 + } 140 141 + if (this.AccountRecovery) { 142 + if (onStatus) onStatus('Validating your private rotation key is in the correct format...') 143 144 + this.recoveryRotationKeyPair = await this.plcOps.getKeyPair(rotationKey, rotationKeyType) 145 146 + if (onStatus) onStatus('Resolving PlC operation logs...') 147 148 + /** @type {Operation} */ 149 + let baseOpForSigning = null 150 + let opPrevCid = null 151 152 + //This is for reversals against a rogue plc op and you want to restore to a specific cid in the audit log 153 + if (cidToRestoreTo) { 154 + let auditLogs = await this.plcOps.getPlcAuditLogs(usersDid) 155 + for (const log of auditLogs) { 156 + if (log.cid === cidToRestoreTo) { 157 + baseOpForSigning = normalizeOp(log.operation) 158 + opPrevCid = log.cid 159 + break 160 + } 161 } 162 + if (!baseOpForSigning) { 163 + throw new Error('Could not find the cid in the audit logs') 164 } 165 + } else { 166 + let { lastOperation, base } = await this.plcOps.getLastPlcOpFromPlc(usersDid) 167 + opPrevCid = base.cid 168 + baseOpForSigning = lastOperation 169 + } 170 171 + if (onStatus) onStatus('Preparing to switch to a temp atproto key...') 172 + if (this.tempVerificationKeypair == null) { 173 + if (onStatus) onStatus('Creating a new temp atproto key...') 174 + this.tempVerificationKeypair = await Secp256k1PrivateKeyExportable.createKeypair() 175 + } 176 + //Just defaulting to the user's recovery key for now. Advance cases will be something else 177 + //Maybe just a new ui to edit the PLC doc in a limited capacity, but sinc ethis is a temp plc op i don't think it's needed 178 + let tempRotationKeys = [this.recoveryRotationKeyPair.didPublicKey] 179 180 + if (onStatus) onStatus('Modifying the PLC OP for recovery...') 181 + //A temp plc op for control of the atproto key to create a serviceAuth and new account on the new PDS 182 + await this.plcOps.signAndPublishNewOp( 183 + usersDid, 184 + this.recoveryRotationKeyPair.keypair, 185 + baseOpForSigning.alsoKnownAs, 186 + tempRotationKeys, 187 + newPDS, 188 + await this.tempVerificationKeypair.exportPublicKey('did'), 189 + opPrevCid, 190 + ) 191 192 + if (onStatus) onStatus('Creating your new account on the new PDS...') 193 + let serviceAuthToken = await this.plcOps.createANewServiceAuthToken( 194 + usersDid, 195 + newHostDesc.data.did, 196 + this.tempVerificationKeypair, 197 + 'com.atproto.server.createAccount', 198 + ) 199 200 + let createAccountRequest = { 201 + did: usersDid, 202 + handle: newHandle, 203 + email: newEmail, 204 + password: newPassword, 205 + } 206 + if (inviteCode) { 207 + createAccountRequest.inviteCode = inviteCode 208 + } 209 + const _ = await this.atpAgent.com.atproto.server.createAccount(createAccountRequest, { 210 + headers: { authorization: `Bearer ${serviceAuthToken}` }, 211 + encoding: 'application/json', 212 + }) 213 + } 214 215 + await this.atpAgent.login({ 216 + identifier: usersDid, 217 + password: newPassword, 218 + }) 219 220 + if (this.AccountRecovery) { 221 + //Moving the user offically to the new PDS 222 + if (onStatus) onStatus('Signing the papers...') 223 + let { base } = await this.plcOps.getLastPlcOpFromPlc(usersDid) 224 + await this.signRestorePlcOperation( 225 + usersDid, 226 + [this.recoveryRotationKeyPair.didPublicKey], 227 + base.cid, 228 + ) 229 + } 230 231 + if (this.RestoreFromBackup) { 232 + if (onStatus) onStatus('Success! Restoring your repo...') 233 + const pdsMoover = new AtpAgent({ service: this.pdsMooverInstance }) 234 + const repoRes = await pdsMoover.com.atproto.sync.getRepo({ did: usersDid }) 235 + await this.atpAgent.com.atproto.repo.importRepo(repoRes.data, { 236 + encoding: 'application/vnd.ipld.car', 237 + }) 238 239 + if (onStatus) onStatus('Restoring your blobs...') 240 241 + //Using the missing endpoint to findout what's missing then the PDS MOOver endpoint to restore 242 + let totalMissingBlobs = 0 243 + let missingBlobCursor = undefined 244 + let missingUploadedBlobs = 0 245 246 + do { 247 + const missingBlobs = await this.atpAgent.com.atproto.repo.listMissingBlobs({ 248 + cursor: missingBlobCursor, 249 + limit: 1000, 250 + }) 251 + totalMissingBlobs += missingBlobs.data.blobs.length 252 253 + for (const recordBlob of missingBlobs.data.blobs) { 254 + try { 255 + const blobRes = await pdsMoover.com.atproto.sync.getBlob({ 256 + did: usersDid, 257 + cid: recordBlob.cid, 258 + }) 259 + let result = await this.atpAgent.com.atproto.repo.uploadBlob(blobRes.data, { 260 + encoding: blobRes.headers['content-type'], 261 + }) 262 263 + if (missingUploadedBlobs % 2 === 0) { 264 + if (onStatus) 265 + onStatus( 266 + `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs} (The total may increase as we find more)`, 267 + ) 268 + } 269 + missingUploadedBlobs++ 270 + } catch (error) { 271 + console.error(error) 272 + } 273 } 274 + missingBlobCursor = missingBlobs.data.cursor 275 + } while (missingBlobCursor) 276 + } 277 + const accountStatus = await this.atpAgent.com.atproto.server.checkAccountStatus() 278 + if (!accountStatus.data.activated) { 279 + if (onStatus) onStatus('Activating your account...') 280 + await this.atpAgent.com.atproto.server.activateAccount() 281 + } 282 + } 283 284 + /** 285 + * This method signs the plc operation over to the new PDS and activates the account 286 + * Assumes you have already created a new account during the recovery process and logged in 287 + * Uses the recommended did doc from the PDS as a base and adds the users rotation key to the rotation keys array 288 + * 289 + * @param usersDid 290 + * @param additionalRotationKeysToAdd 291 + * @param prevCid 292 + * @returns {Promise<void>} 293 + */ 294 + async signRestorePlcOperation(usersDid, additionalRotationKeysToAdd = [], prevCid) { 295 + const getDidCredentials = 296 + await this.atpAgent.com.atproto.identity.getRecommendedDidCredentials() 297 298 + const pdsProvidedRotationKeys = getDidCredentials.data.rotationKeys ?? [] 299 + //Puts the provided rotation keys above the pds pro 300 + const rotationKeys = [ 301 + ...new Set([...(additionalRotationKeysToAdd || []), ...pdsProvidedRotationKeys]), 302 + ] 303 + if (!rotationKeys) { 304 + throw new Error('No rotation keys were found to be added to the PLC') 305 + } 306 307 + if (rotationKeys.length > 5) { 308 + throw new Error('You can only add up to 5 rotation keys to the PLC') 309 + } 310 311 + const plcOpToSubmit = { 312 + type: 'plc_operation', 313 + ...getDidCredentials.data, 314 + prev: prevCid, 315 + rotationKeys: rotationKeys, 316 + } 317 318 + const opBytes = CBOR.encode(plcOpToSubmit) 319 + const sigBytes = await this.recoveryRotationKeyPair.keypair.sign(opBytes) 320 321 + const signature = toBase64Url(sigBytes) 322 323 + const signedOperation = { 324 + ...plcOpToSubmit, 325 + sig: signature, 326 } 327 + 328 + await this.plcOps.pushPlcOperation(usersDid, signedOperation) 329 + await this.atpAgent.com.atproto.server.activateAccount() 330 + } 331 } 332 333 + export { Restore }
+3 -1
packages/moover/package.json
··· 35 "dev": "vite", 36 "build": "vite build", 37 "gen:types": "tsc -p .", 38 - "preview": "vite preview" 39 }, 40 "devDependencies": { 41 "eslint": "^9.34.0", ··· 51 "@atcute/lexicons": "^1.2.2", 52 "@atcute/multibase": "^1.1.6", 53 "@atproto/api": "^0.16.7", 54 "@pds-moover/lexicons": "^1.0.0", 55 "alpinejs": "^3.15.0", 56 "vite-plugin-full-reload": "^1.2.0",
··· 35 "dev": "vite", 36 "build": "vite build", 37 "gen:types": "tsc -p .", 38 + "preview": "vite preview", 39 + "generate:lexicons": "lex build --lexicons lexicons --out lib/lexicons --clear" 40 }, 41 "devDependencies": { 42 "eslint": "^9.34.0", ··· 52 "@atcute/lexicons": "^1.2.2", 53 "@atcute/multibase": "^1.1.6", 54 "@atproto/api": "^0.16.7", 55 + "@atproto/lex": "^0.0.16", 56 "@pds-moover/lexicons": "^1.0.0", 57 "alpinejs": "^3.15.0", 58 "vite-plugin-full-reload": "^1.2.0",
+3342
packages/moover/pnpm-lock.yaml
···
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + '@atcute/cbor': 12 + specifier: ^2.2.7 13 + version: 2.3.0 14 + '@atcute/client': 15 + specifier: ^4.0.4 16 + version: 4.2.1 17 + '@atcute/crypto': 18 + specifier: ^2.2.5 19 + version: 2.3.0 20 + '@atcute/did-plc': 21 + specifier: ^0.1.7 22 + version: 0.1.7 23 + '@atcute/identity-resolver': 24 + specifier: ^1.1.3 25 + version: 1.2.2(@atcute/identity@1.1.3) 26 + '@atcute/lexicons': 27 + specifier: ^1.2.2 28 + version: 1.2.7 29 + '@atcute/multibase': 30 + specifier: ^1.1.6 31 + version: 1.1.7 32 + '@atproto/api': 33 + specifier: ^0.16.7 34 + version: 0.16.11 35 + '@atproto/lex': 36 + specifier: ^0.0.16 37 + version: 0.0.16 38 + '@pds-moover/lexicons': 39 + specifier: ^1.0.0 40 + version: 1.0.1 41 + alpinejs: 42 + specifier: ^3.15.0 43 + version: 3.15.8 44 + vite-plugin-full-reload: 45 + specifier: ^1.2.0 46 + version: 1.2.0 47 + vite-rs-plugin: 48 + specifier: 1.0.1 49 + version: 1.0.1(vite@7.3.1) 50 + devDependencies: 51 + eslint: 52 + specifier: ^9.34.0 53 + version: 9.39.2 54 + eslint-plugin-import: 55 + specifier: ^2.32.0 56 + version: 2.32.0(eslint@9.39.2) 57 + vite: 58 + specifier: ^7.1.7 59 + version: 7.3.1 60 + 61 + packages: 62 + 63 + '@atcute/cbor@2.3.0': 64 + resolution: {integrity: sha512-7G2AndkfYzIXMBOBqUPUWP6oIJJm77KY5nYzS4Mr5NNxnmnrBrXEQqp+seCE3X5TV8FUSWQK5YRTU87uPjafMQ==} 65 + 66 + '@atcute/cid@2.4.0': 67 + resolution: {integrity: sha512-6+5u9MpUrgSRQ94z7vaIX4BYk8fYr2KXUBS+rrr2NhlPy8xam8nbTlmd3hvBbtpSwShbhRAE4tA5Ab7eYUp2Yw==} 68 + 69 + '@atcute/client@4.2.1': 70 + resolution: {integrity: sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw==} 71 + 72 + '@atcute/crypto@2.3.0': 73 + resolution: {integrity: sha512-w5pkJKCjbNMQu+F4JRHbR3ROQyhi1wbn+GSC6WDQamcYHkZmEZk1/eoI354bIQOOfkEM6aFLv718iskrkon4GQ==} 74 + 75 + '@atcute/did-plc@0.1.7': 76 + resolution: {integrity: sha512-a7yOQNqViae3rB5/xa3U0EPJbFD9l8zOHXx6XASZ5F8+Vy2uTgXK3omurpNZ5UxRpy1ni1AMhSohXr61cqWbkg==} 77 + 78 + '@atcute/identity-resolver@1.2.2': 79 + resolution: {integrity: sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw==} 80 + peerDependencies: 81 + '@atcute/identity': ^1.0.0 82 + 83 + '@atcute/identity@1.1.3': 84 + resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} 85 + 86 + '@atcute/lexicons@1.2.7': 87 + resolution: {integrity: sha512-gCvkSMI1F1zx7xXa59iPiSKMH3L5Hga6iurGqQjaQbE2V/np/2QuDqQzt96TNbWfaFAXE9f9oY+0z3ljf/bweA==} 88 + 89 + '@atcute/multibase@1.1.7': 90 + resolution: {integrity: sha512-YmWds7U52b7Qri0xNfGeqSOvgyNfHR8Yy/NNDQx4d5TkCX2fHJIo0pXquEhCyMNAwKt53uH5yQDswy4TNP1Zhw==} 91 + 92 + '@atcute/uint8array@1.1.0': 93 + resolution: {integrity: sha512-JtHXIVW6LPU9FMWp7SgE4HbUs3uV2WdfkK/2RWdEGjr4EgMV50P3FdU6fPeGlTfDNBJVYMIsuD2wwaKRPV/Aqg==} 94 + 95 + '@atcute/util-fetch@1.0.5': 96 + resolution: {integrity: sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig==} 97 + 98 + '@atcute/util-text@1.1.0': 99 + resolution: {integrity: sha512-34G9KD5Z9f7oEdFpZOmqrMnU86p8ne6LlxJowfZzKNszRcl1GH+FtEPh3N1woelJT2SkPXMK2anwT8DESTluwA==} 100 + 101 + '@atproto-labs/did-resolver@0.2.6': 102 + resolution: {integrity: sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg==} 103 + 104 + '@atproto-labs/fetch@0.2.3': 105 + resolution: {integrity: sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==} 106 + 107 + '@atproto-labs/pipe@0.1.1': 108 + resolution: {integrity: sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==} 109 + 110 + '@atproto-labs/simple-store-memory@0.1.4': 111 + resolution: {integrity: sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==} 112 + 113 + '@atproto-labs/simple-store@0.3.0': 114 + resolution: {integrity: sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==} 115 + 116 + '@atproto/api@0.16.11': 117 + resolution: {integrity: sha512-1dhfQNHiclb102RW+Ea8Nft5olfqU0Ev/vlQaSX6mWNo1aP5zT+sPODJ8+BTUOYk3vcuvL7QMkqA/rLYy2PMyw==} 118 + 119 + '@atproto/common-web@0.4.16': 120 + resolution: {integrity: sha512-Ufvaff5JgxUyUyTAG0/3o7ltpy3lnZ1DvLjyAnvAf+hHfiK7OMQg+8byr+orN+KP9MtIQaRTsCgYPX+PxMKUoA==} 121 + 122 + '@atproto/common@0.5.11': 123 + resolution: {integrity: sha512-WRlT4s+wv80WdQuzkQub9D5vTD82O8dH2p91u4b+x3O17q5IQbmA3Lj+1NICINNSy2voqloqAWdqXEkRfdlAPw==} 124 + engines: {node: '>=18.7.0'} 125 + 126 + '@atproto/crypto@0.4.5': 127 + resolution: {integrity: sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw==} 128 + engines: {node: '>=18.7.0'} 129 + 130 + '@atproto/did@0.3.0': 131 + resolution: {integrity: sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA==} 132 + 133 + '@atproto/lex-builder@0.0.15': 134 + resolution: {integrity: sha512-OtAjQD02vTaJNOWFMO5+9o+z3sGEDTb6Gre9/8JsjnweZk9JzZ46rGpK7old8Unw9oQDP/l58uzAGTywV350NQ==} 135 + 136 + '@atproto/lex-cbor@0.0.11': 137 + resolution: {integrity: sha512-A7ETtPsEsJ/VuPJOFw4bPNTKxHvFN1JbTQ2NjLuisd3ry7fVxgMpo/qGXsUQsAh/I/uziGbhpNqdS6GnI2p/Wg==} 138 + 139 + '@atproto/lex-client@0.0.12': 140 + resolution: {integrity: sha512-ef4jQQ7SOtBsXr+Gf1UHuBfCiAGYZxO5PCCXl3eT4ObO83SROtIf7pyO06jBQI/IZChSVsXqXsgakR0aru6lYQ==} 141 + 142 + '@atproto/lex-data@0.0.11': 143 + resolution: {integrity: sha512-4+KTtHdqwlhiTKA7D4SACea4jprsNpCQsNALW09wsZ6IHhCDGO5tr1cmV+QnLYe3G3mu1E1yXHXbPUHrUUDT/A==} 144 + 145 + '@atproto/lex-document@0.0.13': 146 + resolution: {integrity: sha512-LWsBsKIbyuG7jFObTtnCFQNYHxWWVpVVspqv6UtnS/QsaCyCMg1GIz5vlgi8QBnmGvaPiQxIzGt6mERpTvEXpg==} 147 + 148 + '@atproto/lex-installer@0.0.16': 149 + resolution: {integrity: sha512-fHLu8VUf9zf2Jz+wJ9+5fvFGxi3930VyUBk/FtszcccTLg1uzb0qElUpVHpieFAHbA2fYqjvPgCpZpH6iploYQ==} 150 + 151 + '@atproto/lex-json@0.0.11': 152 + resolution: {integrity: sha512-2IExAoQ4KsR5fyPa1JjIvtR316PvdgRH/l3BVGLBd3cSxM3m5MftIv1B6qZ9HjNiK60SgkWp0mi9574bTNDhBQ==} 153 + 154 + '@atproto/lex-resolver@0.0.14': 155 + resolution: {integrity: sha512-jBkYRmMKap2OM1zm0VDvs7Heuf3pGjw9xJEHQx1ohkMmM5f+cHPS40RQ8x8SNE+Vl9gMuOrgmgKyPDIuYSIBTw==} 156 + 157 + '@atproto/lex-schema@0.0.12': 158 + resolution: {integrity: sha512-l1RNYmqNwIEjgMEjC9i2o6FLsUFdpAc610xQYK/CRxN31cRzY0lAJ2GFbp2GZ4rRAD3FGYCXid6gZ42KsieUcw==} 159 + 160 + '@atproto/lex@0.0.16': 161 + resolution: {integrity: sha512-k7cxIFxEiztHVVMFzqOOvThPDZ2RaK8+X9L27v0B2DmNPhbqRoKFdDU3dWHFhUhBP/CrK34zDo8YPQ4JmcradA==} 162 + hasBin: true 163 + 164 + '@atproto/lexicon@0.5.2': 165 + resolution: {integrity: sha512-lRmJgMA8f5j7VB5Iu5cp188ald5FuI4FlmZ7nn6EBrk1dgOstWVrI5Ft6K3z2vjyLZRG6nzknlsw+tDP63p7bQ==} 166 + 167 + '@atproto/lexicon@0.6.1': 168 + resolution: {integrity: sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw==} 169 + 170 + '@atproto/repo@0.8.12': 171 + resolution: {integrity: sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ==} 172 + engines: {node: '>=18.7.0'} 173 + 174 + '@atproto/syntax@0.4.3': 175 + resolution: {integrity: sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA==} 176 + 177 + '@atproto/xrpc@0.7.7': 178 + resolution: {integrity: sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==} 179 + 180 + '@badrap/valita@0.4.6': 181 + resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==} 182 + engines: {node: '>= 18'} 183 + 184 + '@esbuild/aix-ppc64@0.27.3': 185 + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} 186 + engines: {node: '>=18'} 187 + cpu: [ppc64] 188 + os: [aix] 189 + 190 + '@esbuild/android-arm64@0.27.3': 191 + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} 192 + engines: {node: '>=18'} 193 + cpu: [arm64] 194 + os: [android] 195 + 196 + '@esbuild/android-arm@0.27.3': 197 + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} 198 + engines: {node: '>=18'} 199 + cpu: [arm] 200 + os: [android] 201 + 202 + '@esbuild/android-x64@0.27.3': 203 + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} 204 + engines: {node: '>=18'} 205 + cpu: [x64] 206 + os: [android] 207 + 208 + '@esbuild/darwin-arm64@0.27.3': 209 + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} 210 + engines: {node: '>=18'} 211 + cpu: [arm64] 212 + os: [darwin] 213 + 214 + '@esbuild/darwin-x64@0.27.3': 215 + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} 216 + engines: {node: '>=18'} 217 + cpu: [x64] 218 + os: [darwin] 219 + 220 + '@esbuild/freebsd-arm64@0.27.3': 221 + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} 222 + engines: {node: '>=18'} 223 + cpu: [arm64] 224 + os: [freebsd] 225 + 226 + '@esbuild/freebsd-x64@0.27.3': 227 + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} 228 + engines: {node: '>=18'} 229 + cpu: [x64] 230 + os: [freebsd] 231 + 232 + '@esbuild/linux-arm64@0.27.3': 233 + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} 234 + engines: {node: '>=18'} 235 + cpu: [arm64] 236 + os: [linux] 237 + 238 + '@esbuild/linux-arm@0.27.3': 239 + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} 240 + engines: {node: '>=18'} 241 + cpu: [arm] 242 + os: [linux] 243 + 244 + '@esbuild/linux-ia32@0.27.3': 245 + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} 246 + engines: {node: '>=18'} 247 + cpu: [ia32] 248 + os: [linux] 249 + 250 + '@esbuild/linux-loong64@0.27.3': 251 + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} 252 + engines: {node: '>=18'} 253 + cpu: [loong64] 254 + os: [linux] 255 + 256 + '@esbuild/linux-mips64el@0.27.3': 257 + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} 258 + engines: {node: '>=18'} 259 + cpu: [mips64el] 260 + os: [linux] 261 + 262 + '@esbuild/linux-ppc64@0.27.3': 263 + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} 264 + engines: {node: '>=18'} 265 + cpu: [ppc64] 266 + os: [linux] 267 + 268 + '@esbuild/linux-riscv64@0.27.3': 269 + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} 270 + engines: {node: '>=18'} 271 + cpu: [riscv64] 272 + os: [linux] 273 + 274 + '@esbuild/linux-s390x@0.27.3': 275 + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} 276 + engines: {node: '>=18'} 277 + cpu: [s390x] 278 + os: [linux] 279 + 280 + '@esbuild/linux-x64@0.27.3': 281 + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} 282 + engines: {node: '>=18'} 283 + cpu: [x64] 284 + os: [linux] 285 + 286 + '@esbuild/netbsd-arm64@0.27.3': 287 + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} 288 + engines: {node: '>=18'} 289 + cpu: [arm64] 290 + os: [netbsd] 291 + 292 + '@esbuild/netbsd-x64@0.27.3': 293 + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} 294 + engines: {node: '>=18'} 295 + cpu: [x64] 296 + os: [netbsd] 297 + 298 + '@esbuild/openbsd-arm64@0.27.3': 299 + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} 300 + engines: {node: '>=18'} 301 + cpu: [arm64] 302 + os: [openbsd] 303 + 304 + '@esbuild/openbsd-x64@0.27.3': 305 + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} 306 + engines: {node: '>=18'} 307 + cpu: [x64] 308 + os: [openbsd] 309 + 310 + '@esbuild/openharmony-arm64@0.27.3': 311 + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} 312 + engines: {node: '>=18'} 313 + cpu: [arm64] 314 + os: [openharmony] 315 + 316 + '@esbuild/sunos-x64@0.27.3': 317 + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} 318 + engines: {node: '>=18'} 319 + cpu: [x64] 320 + os: [sunos] 321 + 322 + '@esbuild/win32-arm64@0.27.3': 323 + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} 324 + engines: {node: '>=18'} 325 + cpu: [arm64] 326 + os: [win32] 327 + 328 + '@esbuild/win32-ia32@0.27.3': 329 + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} 330 + engines: {node: '>=18'} 331 + cpu: [ia32] 332 + os: [win32] 333 + 334 + '@esbuild/win32-x64@0.27.3': 335 + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} 336 + engines: {node: '>=18'} 337 + cpu: [x64] 338 + os: [win32] 339 + 340 + '@eslint-community/eslint-utils@4.9.1': 341 + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} 342 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 343 + peerDependencies: 344 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 345 + 346 + '@eslint-community/regexpp@4.12.2': 347 + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} 348 + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 349 + 350 + '@eslint/config-array@0.21.1': 351 + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} 352 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 353 + 354 + '@eslint/config-helpers@0.4.2': 355 + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} 356 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 357 + 358 + '@eslint/core@0.17.0': 359 + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} 360 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 361 + 362 + '@eslint/eslintrc@3.3.3': 363 + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} 364 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 365 + 366 + '@eslint/js@9.39.2': 367 + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} 368 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 369 + 370 + '@eslint/object-schema@2.1.7': 371 + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} 372 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 373 + 374 + '@eslint/plugin-kit@0.4.1': 375 + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} 376 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 377 + 378 + '@humanfs/core@0.19.1': 379 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 380 + engines: {node: '>=18.18.0'} 381 + 382 + '@humanfs/node@0.16.7': 383 + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} 384 + engines: {node: '>=18.18.0'} 385 + 386 + '@humanwhocodes/module-importer@1.0.1': 387 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 388 + engines: {node: '>=12.22'} 389 + 390 + '@humanwhocodes/retry@0.4.3': 391 + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} 392 + engines: {node: '>=18.18'} 393 + 394 + '@ipld/dag-cbor@7.0.3': 395 + resolution: {integrity: sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==} 396 + 397 + '@noble/curves@1.9.7': 398 + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} 399 + engines: {node: ^14.21.3 || >=16} 400 + 401 + '@noble/hashes@1.8.0': 402 + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} 403 + engines: {node: ^14.21.3 || >=16} 404 + 405 + '@noble/secp256k1@3.0.0': 406 + resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==} 407 + 408 + '@pds-moover/lexicons@1.0.1': 409 + resolution: {integrity: sha512-fv5b/DtHM7FEo/JklyF9gdK0ainlb6mWjWrBe6cmSAeg9G/4O2jBlQUOqfOAICY9gOcrCpkOrk9PHgGw//JQ2A==} 410 + 411 + '@rollup/rollup-android-arm-eabi@4.57.1': 412 + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} 413 + cpu: [arm] 414 + os: [android] 415 + 416 + '@rollup/rollup-android-arm64@4.57.1': 417 + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} 418 + cpu: [arm64] 419 + os: [android] 420 + 421 + '@rollup/rollup-darwin-arm64@4.57.1': 422 + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} 423 + cpu: [arm64] 424 + os: [darwin] 425 + 426 + '@rollup/rollup-darwin-x64@4.57.1': 427 + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} 428 + cpu: [x64] 429 + os: [darwin] 430 + 431 + '@rollup/rollup-freebsd-arm64@4.57.1': 432 + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} 433 + cpu: [arm64] 434 + os: [freebsd] 435 + 436 + '@rollup/rollup-freebsd-x64@4.57.1': 437 + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} 438 + cpu: [x64] 439 + os: [freebsd] 440 + 441 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 442 + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} 443 + cpu: [arm] 444 + os: [linux] 445 + libc: [glibc] 446 + 447 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 448 + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} 449 + cpu: [arm] 450 + os: [linux] 451 + libc: [musl] 452 + 453 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 454 + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} 455 + cpu: [arm64] 456 + os: [linux] 457 + libc: [glibc] 458 + 459 + '@rollup/rollup-linux-arm64-musl@4.57.1': 460 + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} 461 + cpu: [arm64] 462 + os: [linux] 463 + libc: [musl] 464 + 465 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 466 + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} 467 + cpu: [loong64] 468 + os: [linux] 469 + libc: [glibc] 470 + 471 + '@rollup/rollup-linux-loong64-musl@4.57.1': 472 + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} 473 + cpu: [loong64] 474 + os: [linux] 475 + libc: [musl] 476 + 477 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 478 + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} 479 + cpu: [ppc64] 480 + os: [linux] 481 + libc: [glibc] 482 + 483 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 484 + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} 485 + cpu: [ppc64] 486 + os: [linux] 487 + libc: [musl] 488 + 489 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 490 + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} 491 + cpu: [riscv64] 492 + os: [linux] 493 + libc: [glibc] 494 + 495 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 496 + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} 497 + cpu: [riscv64] 498 + os: [linux] 499 + libc: [musl] 500 + 501 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 502 + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} 503 + cpu: [s390x] 504 + os: [linux] 505 + libc: [glibc] 506 + 507 + '@rollup/rollup-linux-x64-gnu@4.57.1': 508 + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} 509 + cpu: [x64] 510 + os: [linux] 511 + libc: [glibc] 512 + 513 + '@rollup/rollup-linux-x64-musl@4.57.1': 514 + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} 515 + cpu: [x64] 516 + os: [linux] 517 + libc: [musl] 518 + 519 + '@rollup/rollup-openbsd-x64@4.57.1': 520 + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} 521 + cpu: [x64] 522 + os: [openbsd] 523 + 524 + '@rollup/rollup-openharmony-arm64@4.57.1': 525 + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} 526 + cpu: [arm64] 527 + os: [openharmony] 528 + 529 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 530 + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} 531 + cpu: [arm64] 532 + os: [win32] 533 + 534 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 535 + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} 536 + cpu: [ia32] 537 + os: [win32] 538 + 539 + '@rollup/rollup-win32-x64-gnu@4.57.1': 540 + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} 541 + cpu: [x64] 542 + os: [win32] 543 + 544 + '@rollup/rollup-win32-x64-msvc@4.57.1': 545 + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} 546 + cpu: [x64] 547 + os: [win32] 548 + 549 + '@rtsao/scc@1.1.0': 550 + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} 551 + 552 + '@standard-schema/spec@1.1.0': 553 + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} 554 + 555 + '@ts-morph/common@0.28.1': 556 + resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} 557 + 558 + '@types/estree@1.0.8': 559 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 560 + 561 + '@types/json-schema@7.0.15': 562 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 563 + 564 + '@types/json5@0.0.29': 565 + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} 566 + 567 + '@vue/reactivity@3.1.5': 568 + resolution: {integrity: sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==} 569 + 570 + '@vue/shared@3.1.5': 571 + resolution: {integrity: sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==} 572 + 573 + abort-controller@3.0.0: 574 + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 575 + engines: {node: '>=6.5'} 576 + 577 + acorn-jsx@5.3.2: 578 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 579 + peerDependencies: 580 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 581 + 582 + acorn@8.15.0: 583 + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 584 + engines: {node: '>=0.4.0'} 585 + hasBin: true 586 + 587 + ajv@6.12.6: 588 + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 589 + 590 + alpinejs@3.15.8: 591 + resolution: {integrity: sha512-zxIfCRTBGvF1CCLIOMQOxAyBuqibxSEwS6Jm1a3HGA9rgrJVcjEWlwLcQTVGAWGS8YhAsTRLVrtQ5a5QT9bSSQ==} 592 + 593 + ansi-regex@5.0.1: 594 + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 595 + engines: {node: '>=8'} 596 + 597 + ansi-styles@4.3.0: 598 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 599 + engines: {node: '>=8'} 600 + 601 + argparse@2.0.1: 602 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 603 + 604 + array-buffer-byte-length@1.0.2: 605 + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} 606 + engines: {node: '>= 0.4'} 607 + 608 + array-includes@3.1.9: 609 + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} 610 + engines: {node: '>= 0.4'} 611 + 612 + array.prototype.findlastindex@1.2.6: 613 + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} 614 + engines: {node: '>= 0.4'} 615 + 616 + array.prototype.flat@1.3.3: 617 + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} 618 + engines: {node: '>= 0.4'} 619 + 620 + array.prototype.flatmap@1.3.3: 621 + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} 622 + engines: {node: '>= 0.4'} 623 + 624 + arraybuffer.prototype.slice@1.0.4: 625 + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} 626 + engines: {node: '>= 0.4'} 627 + 628 + async-function@1.0.0: 629 + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} 630 + engines: {node: '>= 0.4'} 631 + 632 + atomic-sleep@1.0.0: 633 + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} 634 + engines: {node: '>=8.0.0'} 635 + 636 + available-typed-arrays@1.0.7: 637 + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} 638 + engines: {node: '>= 0.4'} 639 + 640 + await-lock@2.2.2: 641 + resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} 642 + 643 + balanced-match@1.0.2: 644 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 645 + 646 + balanced-match@4.0.3: 647 + resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} 648 + engines: {node: 20 || >=22} 649 + 650 + base64-js@1.5.1: 651 + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 652 + 653 + brace-expansion@1.1.12: 654 + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} 655 + 656 + brace-expansion@5.0.2: 657 + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} 658 + engines: {node: 20 || >=22} 659 + 660 + buffer@6.0.3: 661 + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 662 + 663 + call-bind-apply-helpers@1.0.2: 664 + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 665 + engines: {node: '>= 0.4'} 666 + 667 + call-bind@1.0.8: 668 + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} 669 + engines: {node: '>= 0.4'} 670 + 671 + call-bound@1.0.4: 672 + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 673 + engines: {node: '>= 0.4'} 674 + 675 + callsites@3.1.0: 676 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 677 + engines: {node: '>=6'} 678 + 679 + cborg@1.10.2: 680 + resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} 681 + hasBin: true 682 + 683 + chalk@4.1.2: 684 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 685 + engines: {node: '>=10'} 686 + 687 + cliui@8.0.1: 688 + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 689 + engines: {node: '>=12'} 690 + 691 + code-block-writer@13.0.3: 692 + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} 693 + 694 + color-convert@2.0.1: 695 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 696 + engines: {node: '>=7.0.0'} 697 + 698 + color-name@1.1.4: 699 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 700 + 701 + concat-map@0.0.1: 702 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 703 + 704 + core-js@3.48.0: 705 + resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} 706 + 707 + cross-spawn@7.0.6: 708 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 709 + engines: {node: '>= 8'} 710 + 711 + data-view-buffer@1.0.2: 712 + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} 713 + engines: {node: '>= 0.4'} 714 + 715 + data-view-byte-length@1.0.2: 716 + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} 717 + engines: {node: '>= 0.4'} 718 + 719 + data-view-byte-offset@1.0.1: 720 + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} 721 + engines: {node: '>= 0.4'} 722 + 723 + debug@3.2.7: 724 + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} 725 + peerDependencies: 726 + supports-color: '*' 727 + peerDependenciesMeta: 728 + supports-color: 729 + optional: true 730 + 731 + debug@4.4.3: 732 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} 733 + engines: {node: '>=6.0'} 734 + peerDependencies: 735 + supports-color: '*' 736 + peerDependenciesMeta: 737 + supports-color: 738 + optional: true 739 + 740 + deep-is@0.1.4: 741 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 742 + 743 + define-data-property@1.1.4: 744 + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 745 + engines: {node: '>= 0.4'} 746 + 747 + define-properties@1.2.1: 748 + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 749 + engines: {node: '>= 0.4'} 750 + 751 + doctrine@2.1.0: 752 + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 753 + engines: {node: '>=0.10.0'} 754 + 755 + dunder-proto@1.0.1: 756 + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 757 + engines: {node: '>= 0.4'} 758 + 759 + emoji-regex@8.0.0: 760 + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 761 + 762 + es-abstract@1.24.1: 763 + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} 764 + engines: {node: '>= 0.4'} 765 + 766 + es-define-property@1.0.1: 767 + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 768 + engines: {node: '>= 0.4'} 769 + 770 + es-errors@1.3.0: 771 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 772 + engines: {node: '>= 0.4'} 773 + 774 + es-object-atoms@1.1.1: 775 + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 776 + engines: {node: '>= 0.4'} 777 + 778 + es-set-tostringtag@2.1.0: 779 + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} 780 + engines: {node: '>= 0.4'} 781 + 782 + es-shim-unscopables@1.1.0: 783 + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} 784 + engines: {node: '>= 0.4'} 785 + 786 + es-to-primitive@1.3.0: 787 + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} 788 + engines: {node: '>= 0.4'} 789 + 790 + esbuild@0.27.3: 791 + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} 792 + engines: {node: '>=18'} 793 + hasBin: true 794 + 795 + escalade@3.2.0: 796 + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 797 + engines: {node: '>=6'} 798 + 799 + escape-string-regexp@4.0.0: 800 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 801 + engines: {node: '>=10'} 802 + 803 + eslint-import-resolver-node@0.3.9: 804 + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} 805 + 806 + eslint-module-utils@2.12.1: 807 + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} 808 + engines: {node: '>=4'} 809 + peerDependencies: 810 + '@typescript-eslint/parser': '*' 811 + eslint: '*' 812 + eslint-import-resolver-node: '*' 813 + eslint-import-resolver-typescript: '*' 814 + eslint-import-resolver-webpack: '*' 815 + peerDependenciesMeta: 816 + '@typescript-eslint/parser': 817 + optional: true 818 + eslint: 819 + optional: true 820 + eslint-import-resolver-node: 821 + optional: true 822 + eslint-import-resolver-typescript: 823 + optional: true 824 + eslint-import-resolver-webpack: 825 + optional: true 826 + 827 + eslint-plugin-import@2.32.0: 828 + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} 829 + engines: {node: '>=4'} 830 + peerDependencies: 831 + '@typescript-eslint/parser': '*' 832 + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 833 + peerDependenciesMeta: 834 + '@typescript-eslint/parser': 835 + optional: true 836 + 837 + eslint-scope@8.4.0: 838 + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} 839 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 840 + 841 + eslint-visitor-keys@3.4.3: 842 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 843 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 844 + 845 + eslint-visitor-keys@4.2.1: 846 + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} 847 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 848 + 849 + eslint@9.39.2: 850 + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} 851 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 852 + hasBin: true 853 + peerDependencies: 854 + jiti: '*' 855 + peerDependenciesMeta: 856 + jiti: 857 + optional: true 858 + 859 + esm-env@1.2.2: 860 + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 861 + 862 + espree@10.4.0: 863 + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} 864 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 865 + 866 + esquery@1.7.0: 867 + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} 868 + engines: {node: '>=0.10'} 869 + 870 + esrecurse@4.3.0: 871 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 872 + engines: {node: '>=4.0'} 873 + 874 + estraverse@5.3.0: 875 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 876 + engines: {node: '>=4.0'} 877 + 878 + esutils@2.0.3: 879 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 880 + engines: {node: '>=0.10.0'} 881 + 882 + event-target-shim@5.0.1: 883 + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 884 + engines: {node: '>=6'} 885 + 886 + events@3.3.0: 887 + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 888 + engines: {node: '>=0.8.x'} 889 + 890 + fast-deep-equal@3.1.3: 891 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 892 + 893 + fast-json-stable-stringify@2.1.0: 894 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 895 + 896 + fast-levenshtein@2.0.6: 897 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 898 + 899 + fast-redact@3.5.0: 900 + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} 901 + engines: {node: '>=6'} 902 + 903 + fdir@6.5.0: 904 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 905 + engines: {node: '>=12.0.0'} 906 + peerDependencies: 907 + picomatch: ^3 || ^4 908 + peerDependenciesMeta: 909 + picomatch: 910 + optional: true 911 + 912 + file-entry-cache@8.0.0: 913 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 914 + engines: {node: '>=16.0.0'} 915 + 916 + find-up@5.0.0: 917 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 918 + engines: {node: '>=10'} 919 + 920 + flat-cache@4.0.1: 921 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 922 + engines: {node: '>=16'} 923 + 924 + flatted@3.3.3: 925 + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 926 + 927 + for-each@0.3.5: 928 + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} 929 + engines: {node: '>= 0.4'} 930 + 931 + fsevents@2.3.3: 932 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 933 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 934 + os: [darwin] 935 + 936 + function-bind@1.1.2: 937 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 938 + 939 + function.prototype.name@1.1.8: 940 + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} 941 + engines: {node: '>= 0.4'} 942 + 943 + functions-have-names@1.2.3: 944 + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 945 + 946 + generator-function@2.0.1: 947 + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} 948 + engines: {node: '>= 0.4'} 949 + 950 + get-caller-file@2.0.5: 951 + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 952 + engines: {node: 6.* || 8.* || >= 10.*} 953 + 954 + get-intrinsic@1.3.0: 955 + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 956 + engines: {node: '>= 0.4'} 957 + 958 + get-proto@1.0.1: 959 + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 960 + engines: {node: '>= 0.4'} 961 + 962 + get-symbol-description@1.1.0: 963 + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} 964 + engines: {node: '>= 0.4'} 965 + 966 + glob-parent@6.0.2: 967 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 968 + engines: {node: '>=10.13.0'} 969 + 970 + globals@14.0.0: 971 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 972 + engines: {node: '>=18'} 973 + 974 + globalthis@1.0.4: 975 + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} 976 + engines: {node: '>= 0.4'} 977 + 978 + gopd@1.2.0: 979 + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 980 + engines: {node: '>= 0.4'} 981 + 982 + has-bigints@1.1.0: 983 + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} 984 + engines: {node: '>= 0.4'} 985 + 986 + has-flag@4.0.0: 987 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 988 + engines: {node: '>=8'} 989 + 990 + has-property-descriptors@1.0.2: 991 + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 992 + 993 + has-proto@1.2.0: 994 + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} 995 + engines: {node: '>= 0.4'} 996 + 997 + has-symbols@1.1.0: 998 + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 999 + engines: {node: '>= 0.4'} 1000 + 1001 + has-tostringtag@1.0.2: 1002 + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 1003 + engines: {node: '>= 0.4'} 1004 + 1005 + hasown@2.0.2: 1006 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 1007 + engines: {node: '>= 0.4'} 1008 + 1009 + ieee754@1.2.1: 1010 + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 1011 + 1012 + ignore@5.3.2: 1013 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 1014 + engines: {node: '>= 4'} 1015 + 1016 + import-fresh@3.3.1: 1017 + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 1018 + engines: {node: '>=6'} 1019 + 1020 + imurmurhash@0.1.4: 1021 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 1022 + engines: {node: '>=0.8.19'} 1023 + 1024 + internal-slot@1.1.0: 1025 + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} 1026 + engines: {node: '>= 0.4'} 1027 + 1028 + is-array-buffer@3.0.5: 1029 + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} 1030 + engines: {node: '>= 0.4'} 1031 + 1032 + is-async-function@2.1.1: 1033 + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} 1034 + engines: {node: '>= 0.4'} 1035 + 1036 + is-bigint@1.1.0: 1037 + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} 1038 + engines: {node: '>= 0.4'} 1039 + 1040 + is-boolean-object@1.2.2: 1041 + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} 1042 + engines: {node: '>= 0.4'} 1043 + 1044 + is-callable@1.2.7: 1045 + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 1046 + engines: {node: '>= 0.4'} 1047 + 1048 + is-core-module@2.16.1: 1049 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 1050 + engines: {node: '>= 0.4'} 1051 + 1052 + is-data-view@1.0.2: 1053 + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} 1054 + engines: {node: '>= 0.4'} 1055 + 1056 + is-date-object@1.1.0: 1057 + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} 1058 + engines: {node: '>= 0.4'} 1059 + 1060 + is-extglob@2.1.1: 1061 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1062 + engines: {node: '>=0.10.0'} 1063 + 1064 + is-finalizationregistry@1.1.1: 1065 + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} 1066 + engines: {node: '>= 0.4'} 1067 + 1068 + is-fullwidth-code-point@3.0.0: 1069 + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 1070 + engines: {node: '>=8'} 1071 + 1072 + is-generator-function@1.1.2: 1073 + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} 1074 + engines: {node: '>= 0.4'} 1075 + 1076 + is-glob@4.0.3: 1077 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1078 + engines: {node: '>=0.10.0'} 1079 + 1080 + is-map@2.0.3: 1081 + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} 1082 + engines: {node: '>= 0.4'} 1083 + 1084 + is-negative-zero@2.0.3: 1085 + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} 1086 + engines: {node: '>= 0.4'} 1087 + 1088 + is-number-object@1.1.1: 1089 + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} 1090 + engines: {node: '>= 0.4'} 1091 + 1092 + is-regex@1.2.1: 1093 + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} 1094 + engines: {node: '>= 0.4'} 1095 + 1096 + is-set@2.0.3: 1097 + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} 1098 + engines: {node: '>= 0.4'} 1099 + 1100 + is-shared-array-buffer@1.0.4: 1101 + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} 1102 + engines: {node: '>= 0.4'} 1103 + 1104 + is-string@1.1.1: 1105 + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} 1106 + engines: {node: '>= 0.4'} 1107 + 1108 + is-symbol@1.1.1: 1109 + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} 1110 + engines: {node: '>= 0.4'} 1111 + 1112 + is-typed-array@1.1.15: 1113 + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} 1114 + engines: {node: '>= 0.4'} 1115 + 1116 + is-weakmap@2.0.2: 1117 + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} 1118 + engines: {node: '>= 0.4'} 1119 + 1120 + is-weakref@1.1.1: 1121 + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} 1122 + engines: {node: '>= 0.4'} 1123 + 1124 + is-weakset@2.0.4: 1125 + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} 1126 + engines: {node: '>= 0.4'} 1127 + 1128 + isarray@2.0.5: 1129 + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 1130 + 1131 + isexe@2.0.0: 1132 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1133 + 1134 + iso-datestring-validator@2.2.2: 1135 + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} 1136 + 1137 + js-yaml@4.1.1: 1138 + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 1139 + hasBin: true 1140 + 1141 + json-buffer@3.0.1: 1142 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 1143 + 1144 + json-schema-traverse@0.4.1: 1145 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1146 + 1147 + json-stable-stringify-without-jsonify@1.0.1: 1148 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1149 + 1150 + json5@1.0.2: 1151 + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} 1152 + hasBin: true 1153 + 1154 + keyv@4.5.4: 1155 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 1156 + 1157 + levn@0.4.1: 1158 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1159 + engines: {node: '>= 0.8.0'} 1160 + 1161 + locate-path@6.0.0: 1162 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1163 + engines: {node: '>=10'} 1164 + 1165 + lodash.merge@4.6.2: 1166 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1167 + 1168 + lru-cache@10.4.3: 1169 + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 1170 + 1171 + math-intrinsics@1.1.0: 1172 + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 1173 + engines: {node: '>= 0.4'} 1174 + 1175 + minimatch@10.2.1: 1176 + resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==} 1177 + engines: {node: 20 || >=22} 1178 + 1179 + minimatch@3.1.2: 1180 + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1181 + 1182 + minimist@1.2.8: 1183 + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 1184 + 1185 + ms@2.1.3: 1186 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1187 + 1188 + multiformats@9.9.0: 1189 + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} 1190 + 1191 + nanoid@3.3.11: 1192 + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 1193 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1194 + hasBin: true 1195 + 1196 + natural-compare@1.4.0: 1197 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1198 + 1199 + object-inspect@1.13.4: 1200 + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 1201 + engines: {node: '>= 0.4'} 1202 + 1203 + object-keys@1.1.1: 1204 + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1205 + engines: {node: '>= 0.4'} 1206 + 1207 + object.assign@4.1.7: 1208 + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} 1209 + engines: {node: '>= 0.4'} 1210 + 1211 + object.fromentries@2.0.8: 1212 + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} 1213 + engines: {node: '>= 0.4'} 1214 + 1215 + object.groupby@1.0.3: 1216 + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} 1217 + engines: {node: '>= 0.4'} 1218 + 1219 + object.values@1.2.1: 1220 + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} 1221 + engines: {node: '>= 0.4'} 1222 + 1223 + on-exit-leak-free@2.1.2: 1224 + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} 1225 + engines: {node: '>=14.0.0'} 1226 + 1227 + optionator@0.9.4: 1228 + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 1229 + engines: {node: '>= 0.8.0'} 1230 + 1231 + own-keys@1.0.1: 1232 + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} 1233 + engines: {node: '>= 0.4'} 1234 + 1235 + p-limit@3.1.0: 1236 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1237 + engines: {node: '>=10'} 1238 + 1239 + p-locate@5.0.0: 1240 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1241 + engines: {node: '>=10'} 1242 + 1243 + parent-module@1.0.1: 1244 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1245 + engines: {node: '>=6'} 1246 + 1247 + path-browserify@1.0.1: 1248 + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} 1249 + 1250 + path-exists@4.0.0: 1251 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1252 + engines: {node: '>=8'} 1253 + 1254 + path-key@3.1.1: 1255 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1256 + engines: {node: '>=8'} 1257 + 1258 + path-parse@1.0.7: 1259 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1260 + 1261 + picocolors@1.1.1: 1262 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 1263 + 1264 + picomatch@2.3.1: 1265 + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1266 + engines: {node: '>=8.6'} 1267 + 1268 + picomatch@4.0.3: 1269 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1270 + engines: {node: '>=12'} 1271 + 1272 + pino-abstract-transport@1.2.0: 1273 + resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} 1274 + 1275 + pino-std-serializers@6.2.2: 1276 + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} 1277 + 1278 + pino@8.21.0: 1279 + resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} 1280 + hasBin: true 1281 + 1282 + possible-typed-array-names@1.1.0: 1283 + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} 1284 + engines: {node: '>= 0.4'} 1285 + 1286 + postcss@8.5.6: 1287 + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1288 + engines: {node: ^10 || ^12 || >=14} 1289 + 1290 + prelude-ls@1.2.1: 1291 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1292 + engines: {node: '>= 0.8.0'} 1293 + 1294 + prettier@3.8.1: 1295 + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} 1296 + engines: {node: '>=14'} 1297 + hasBin: true 1298 + 1299 + process-warning@3.0.0: 1300 + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} 1301 + 1302 + process@0.11.10: 1303 + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 1304 + engines: {node: '>= 0.6.0'} 1305 + 1306 + punycode@2.3.1: 1307 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1308 + engines: {node: '>=6'} 1309 + 1310 + quick-format-unescaped@4.0.4: 1311 + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} 1312 + 1313 + readable-stream@4.7.0: 1314 + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} 1315 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1316 + 1317 + real-require@0.2.0: 1318 + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} 1319 + engines: {node: '>= 12.13.0'} 1320 + 1321 + reflect.getprototypeof@1.0.10: 1322 + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} 1323 + engines: {node: '>= 0.4'} 1324 + 1325 + regexp.prototype.flags@1.5.4: 1326 + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} 1327 + engines: {node: '>= 0.4'} 1328 + 1329 + require-directory@2.1.1: 1330 + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 1331 + engines: {node: '>=0.10.0'} 1332 + 1333 + resolve-from@4.0.0: 1334 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1335 + engines: {node: '>=4'} 1336 + 1337 + resolve@1.22.11: 1338 + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} 1339 + engines: {node: '>= 0.4'} 1340 + hasBin: true 1341 + 1342 + rollup@4.57.1: 1343 + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} 1344 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1345 + hasBin: true 1346 + 1347 + safe-array-concat@1.1.3: 1348 + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} 1349 + engines: {node: '>=0.4'} 1350 + 1351 + safe-buffer@5.2.1: 1352 + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1353 + 1354 + safe-push-apply@1.0.0: 1355 + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} 1356 + engines: {node: '>= 0.4'} 1357 + 1358 + safe-regex-test@1.1.0: 1359 + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} 1360 + engines: {node: '>= 0.4'} 1361 + 1362 + safe-stable-stringify@2.5.0: 1363 + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} 1364 + engines: {node: '>=10'} 1365 + 1366 + semver@6.3.1: 1367 + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1368 + hasBin: true 1369 + 1370 + set-function-length@1.2.2: 1371 + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 1372 + engines: {node: '>= 0.4'} 1373 + 1374 + set-function-name@2.0.2: 1375 + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} 1376 + engines: {node: '>= 0.4'} 1377 + 1378 + set-proto@1.0.0: 1379 + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} 1380 + engines: {node: '>= 0.4'} 1381 + 1382 + shebang-command@2.0.0: 1383 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1384 + engines: {node: '>=8'} 1385 + 1386 + shebang-regex@3.0.0: 1387 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1388 + engines: {node: '>=8'} 1389 + 1390 + side-channel-list@1.0.0: 1391 + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 1392 + engines: {node: '>= 0.4'} 1393 + 1394 + side-channel-map@1.0.1: 1395 + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 1396 + engines: {node: '>= 0.4'} 1397 + 1398 + side-channel-weakmap@1.0.2: 1399 + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 1400 + engines: {node: '>= 0.4'} 1401 + 1402 + side-channel@1.1.0: 1403 + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 1404 + engines: {node: '>= 0.4'} 1405 + 1406 + sonic-boom@3.8.1: 1407 + resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} 1408 + 1409 + source-map-js@1.2.1: 1410 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1411 + engines: {node: '>=0.10.0'} 1412 + 1413 + split2@4.2.0: 1414 + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 1415 + engines: {node: '>= 10.x'} 1416 + 1417 + stop-iteration-iterator@1.1.0: 1418 + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} 1419 + engines: {node: '>= 0.4'} 1420 + 1421 + string-width@4.2.3: 1422 + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1423 + engines: {node: '>=8'} 1424 + 1425 + string.prototype.trim@1.2.10: 1426 + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} 1427 + engines: {node: '>= 0.4'} 1428 + 1429 + string.prototype.trimend@1.0.9: 1430 + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} 1431 + engines: {node: '>= 0.4'} 1432 + 1433 + string.prototype.trimstart@1.0.8: 1434 + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} 1435 + engines: {node: '>= 0.4'} 1436 + 1437 + string_decoder@1.3.0: 1438 + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1439 + 1440 + strip-ansi@6.0.1: 1441 + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1442 + engines: {node: '>=8'} 1443 + 1444 + strip-bom@3.0.0: 1445 + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} 1446 + engines: {node: '>=4'} 1447 + 1448 + strip-json-comments@3.1.1: 1449 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1450 + engines: {node: '>=8'} 1451 + 1452 + supports-color@7.2.0: 1453 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1454 + engines: {node: '>=8'} 1455 + 1456 + supports-preserve-symlinks-flag@1.0.0: 1457 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1458 + engines: {node: '>= 0.4'} 1459 + 1460 + thread-stream@2.7.0: 1461 + resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} 1462 + 1463 + tinyglobby@0.2.15: 1464 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 1465 + engines: {node: '>=12.0.0'} 1466 + 1467 + tlds@1.261.0: 1468 + resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} 1469 + hasBin: true 1470 + 1471 + ts-morph@27.0.2: 1472 + resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} 1473 + 1474 + tsconfig-paths@3.15.0: 1475 + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} 1476 + 1477 + tslib@2.8.1: 1478 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1479 + 1480 + type-check@0.4.0: 1481 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1482 + engines: {node: '>= 0.8.0'} 1483 + 1484 + typed-array-buffer@1.0.3: 1485 + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} 1486 + engines: {node: '>= 0.4'} 1487 + 1488 + typed-array-byte-length@1.0.3: 1489 + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} 1490 + engines: {node: '>= 0.4'} 1491 + 1492 + typed-array-byte-offset@1.0.4: 1493 + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} 1494 + engines: {node: '>= 0.4'} 1495 + 1496 + typed-array-length@1.0.7: 1497 + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} 1498 + engines: {node: '>= 0.4'} 1499 + 1500 + uint8arrays@3.0.0: 1501 + resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} 1502 + 1503 + unbox-primitive@1.1.0: 1504 + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} 1505 + engines: {node: '>= 0.4'} 1506 + 1507 + unicode-segmenter@0.14.5: 1508 + resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} 1509 + 1510 + uri-js@4.4.1: 1511 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1512 + 1513 + varint@6.0.0: 1514 + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} 1515 + 1516 + vite-plugin-full-reload@1.2.0: 1517 + resolution: {integrity: sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==} 1518 + 1519 + vite-rs-plugin@1.0.1: 1520 + resolution: {integrity: sha512-YhgflKQIRzuS5x66J3yICoVLH25D2fNU+jThK8tpYl/jGrXeIKT4w5VH1lkLPRC0SjK2ZCm9S6K9Z2ZFVDHjPQ==} 1521 + hasBin: true 1522 + peerDependencies: 1523 + vite: ^5.0.0 1524 + 1525 + vite@7.3.1: 1526 + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} 1527 + engines: {node: ^20.19.0 || >=22.12.0} 1528 + hasBin: true 1529 + peerDependencies: 1530 + '@types/node': ^20.19.0 || >=22.12.0 1531 + jiti: '>=1.21.0' 1532 + less: ^4.0.0 1533 + lightningcss: ^1.21.0 1534 + sass: ^1.70.0 1535 + sass-embedded: ^1.70.0 1536 + stylus: '>=0.54.8' 1537 + sugarss: ^5.0.0 1538 + terser: ^5.16.0 1539 + tsx: ^4.8.1 1540 + yaml: ^2.4.2 1541 + peerDependenciesMeta: 1542 + '@types/node': 1543 + optional: true 1544 + jiti: 1545 + optional: true 1546 + less: 1547 + optional: true 1548 + lightningcss: 1549 + optional: true 1550 + sass: 1551 + optional: true 1552 + sass-embedded: 1553 + optional: true 1554 + stylus: 1555 + optional: true 1556 + sugarss: 1557 + optional: true 1558 + terser: 1559 + optional: true 1560 + tsx: 1561 + optional: true 1562 + yaml: 1563 + optional: true 1564 + 1565 + which-boxed-primitive@1.1.1: 1566 + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} 1567 + engines: {node: '>= 0.4'} 1568 + 1569 + which-builtin-type@1.2.1: 1570 + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} 1571 + engines: {node: '>= 0.4'} 1572 + 1573 + which-collection@1.0.2: 1574 + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} 1575 + engines: {node: '>= 0.4'} 1576 + 1577 + which-typed-array@1.1.20: 1578 + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} 1579 + engines: {node: '>= 0.4'} 1580 + 1581 + which@2.0.2: 1582 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1583 + engines: {node: '>= 8'} 1584 + hasBin: true 1585 + 1586 + word-wrap@1.2.5: 1587 + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 1588 + engines: {node: '>=0.10.0'} 1589 + 1590 + wrap-ansi@7.0.0: 1591 + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1592 + engines: {node: '>=10'} 1593 + 1594 + y18n@5.0.8: 1595 + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 1596 + engines: {node: '>=10'} 1597 + 1598 + yargs-parser@21.1.1: 1599 + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 1600 + engines: {node: '>=12'} 1601 + 1602 + yargs@17.7.2: 1603 + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 1604 + engines: {node: '>=12'} 1605 + 1606 + yocto-queue@0.1.0: 1607 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1608 + engines: {node: '>=10'} 1609 + 1610 + zod@3.25.76: 1611 + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} 1612 + 1613 + snapshots: 1614 + 1615 + '@atcute/cbor@2.3.0': 1616 + dependencies: 1617 + '@atcute/cid': 2.4.0 1618 + '@atcute/multibase': 1.1.7 1619 + '@atcute/uint8array': 1.1.0 1620 + 1621 + '@atcute/cid@2.4.0': 1622 + dependencies: 1623 + '@atcute/multibase': 1.1.7 1624 + '@atcute/uint8array': 1.1.0 1625 + 1626 + '@atcute/client@4.2.1': 1627 + dependencies: 1628 + '@atcute/identity': 1.1.3 1629 + '@atcute/lexicons': 1.2.7 1630 + 1631 + '@atcute/crypto@2.3.0': 1632 + dependencies: 1633 + '@atcute/multibase': 1.1.7 1634 + '@atcute/uint8array': 1.1.0 1635 + '@noble/secp256k1': 3.0.0 1636 + 1637 + '@atcute/did-plc@0.1.7': 1638 + dependencies: 1639 + '@atcute/cbor': 2.3.0 1640 + '@atcute/cid': 2.4.0 1641 + '@atcute/crypto': 2.3.0 1642 + '@atcute/identity': 1.1.3 1643 + '@atcute/lexicons': 1.2.7 1644 + '@atcute/multibase': 1.1.7 1645 + '@atcute/uint8array': 1.1.0 1646 + '@badrap/valita': 0.4.6 1647 + 1648 + '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3)': 1649 + dependencies: 1650 + '@atcute/identity': 1.1.3 1651 + '@atcute/lexicons': 1.2.7 1652 + '@atcute/util-fetch': 1.0.5 1653 + '@badrap/valita': 0.4.6 1654 + 1655 + '@atcute/identity@1.1.3': 1656 + dependencies: 1657 + '@atcute/lexicons': 1.2.7 1658 + '@badrap/valita': 0.4.6 1659 + 1660 + '@atcute/lexicons@1.2.7': 1661 + dependencies: 1662 + '@atcute/uint8array': 1.1.0 1663 + '@atcute/util-text': 1.1.0 1664 + '@standard-schema/spec': 1.1.0 1665 + esm-env: 1.2.2 1666 + 1667 + '@atcute/multibase@1.1.7': 1668 + dependencies: 1669 + '@atcute/uint8array': 1.1.0 1670 + 1671 + '@atcute/uint8array@1.1.0': {} 1672 + 1673 + '@atcute/util-fetch@1.0.5': 1674 + dependencies: 1675 + '@badrap/valita': 0.4.6 1676 + 1677 + '@atcute/util-text@1.1.0': 1678 + dependencies: 1679 + unicode-segmenter: 0.14.5 1680 + 1681 + '@atproto-labs/did-resolver@0.2.6': 1682 + dependencies: 1683 + '@atproto-labs/fetch': 0.2.3 1684 + '@atproto-labs/pipe': 0.1.1 1685 + '@atproto-labs/simple-store': 0.3.0 1686 + '@atproto-labs/simple-store-memory': 0.1.4 1687 + '@atproto/did': 0.3.0 1688 + zod: 3.25.76 1689 + 1690 + '@atproto-labs/fetch@0.2.3': 1691 + dependencies: 1692 + '@atproto-labs/pipe': 0.1.1 1693 + 1694 + '@atproto-labs/pipe@0.1.1': {} 1695 + 1696 + '@atproto-labs/simple-store-memory@0.1.4': 1697 + dependencies: 1698 + '@atproto-labs/simple-store': 0.3.0 1699 + lru-cache: 10.4.3 1700 + 1701 + '@atproto-labs/simple-store@0.3.0': {} 1702 + 1703 + '@atproto/api@0.16.11': 1704 + dependencies: 1705 + '@atproto/common-web': 0.4.16 1706 + '@atproto/lexicon': 0.5.2 1707 + '@atproto/syntax': 0.4.3 1708 + '@atproto/xrpc': 0.7.7 1709 + await-lock: 2.2.2 1710 + multiformats: 9.9.0 1711 + tlds: 1.261.0 1712 + zod: 3.25.76 1713 + 1714 + '@atproto/common-web@0.4.16': 1715 + dependencies: 1716 + '@atproto/lex-data': 0.0.11 1717 + '@atproto/lex-json': 0.0.11 1718 + '@atproto/syntax': 0.4.3 1719 + zod: 3.25.76 1720 + 1721 + '@atproto/common@0.5.11': 1722 + dependencies: 1723 + '@atproto/common-web': 0.4.16 1724 + '@atproto/lex-cbor': 0.0.11 1725 + '@atproto/lex-data': 0.0.11 1726 + iso-datestring-validator: 2.2.2 1727 + multiformats: 9.9.0 1728 + pino: 8.21.0 1729 + 1730 + '@atproto/crypto@0.4.5': 1731 + dependencies: 1732 + '@noble/curves': 1.9.7 1733 + '@noble/hashes': 1.8.0 1734 + uint8arrays: 3.0.0 1735 + 1736 + '@atproto/did@0.3.0': 1737 + dependencies: 1738 + zod: 3.25.76 1739 + 1740 + '@atproto/lex-builder@0.0.15': 1741 + dependencies: 1742 + '@atproto/lex-document': 0.0.13 1743 + '@atproto/lex-schema': 0.0.12 1744 + prettier: 3.8.1 1745 + ts-morph: 27.0.2 1746 + tslib: 2.8.1 1747 + 1748 + '@atproto/lex-cbor@0.0.11': 1749 + dependencies: 1750 + '@atproto/lex-data': 0.0.11 1751 + tslib: 2.8.1 1752 + 1753 + '@atproto/lex-client@0.0.12': 1754 + dependencies: 1755 + '@atproto/lex-data': 0.0.11 1756 + '@atproto/lex-json': 0.0.11 1757 + '@atproto/lex-schema': 0.0.12 1758 + tslib: 2.8.1 1759 + 1760 + '@atproto/lex-data@0.0.11': 1761 + dependencies: 1762 + multiformats: 9.9.0 1763 + tslib: 2.8.1 1764 + uint8arrays: 3.0.0 1765 + unicode-segmenter: 0.14.5 1766 + 1767 + '@atproto/lex-document@0.0.13': 1768 + dependencies: 1769 + '@atproto/lex-schema': 0.0.12 1770 + core-js: 3.48.0 1771 + tslib: 2.8.1 1772 + 1773 + '@atproto/lex-installer@0.0.16': 1774 + dependencies: 1775 + '@atproto/lex-builder': 0.0.15 1776 + '@atproto/lex-cbor': 0.0.11 1777 + '@atproto/lex-data': 0.0.11 1778 + '@atproto/lex-document': 0.0.13 1779 + '@atproto/lex-resolver': 0.0.14 1780 + '@atproto/lex-schema': 0.0.12 1781 + '@atproto/syntax': 0.4.3 1782 + tslib: 2.8.1 1783 + 1784 + '@atproto/lex-json@0.0.11': 1785 + dependencies: 1786 + '@atproto/lex-data': 0.0.11 1787 + tslib: 2.8.1 1788 + 1789 + '@atproto/lex-resolver@0.0.14': 1790 + dependencies: 1791 + '@atproto-labs/did-resolver': 0.2.6 1792 + '@atproto/crypto': 0.4.5 1793 + '@atproto/lex-client': 0.0.12 1794 + '@atproto/lex-data': 0.0.11 1795 + '@atproto/lex-document': 0.0.13 1796 + '@atproto/lex-schema': 0.0.12 1797 + '@atproto/repo': 0.8.12 1798 + '@atproto/syntax': 0.4.3 1799 + tslib: 2.8.1 1800 + 1801 + '@atproto/lex-schema@0.0.12': 1802 + dependencies: 1803 + '@atproto/lex-data': 0.0.11 1804 + '@atproto/syntax': 0.4.3 1805 + tslib: 2.8.1 1806 + 1807 + '@atproto/lex@0.0.16': 1808 + dependencies: 1809 + '@atproto/lex-builder': 0.0.15 1810 + '@atproto/lex-client': 0.0.12 1811 + '@atproto/lex-data': 0.0.11 1812 + '@atproto/lex-installer': 0.0.16 1813 + '@atproto/lex-json': 0.0.11 1814 + '@atproto/lex-schema': 0.0.12 1815 + tslib: 2.8.1 1816 + yargs: 17.7.2 1817 + 1818 + '@atproto/lexicon@0.5.2': 1819 + dependencies: 1820 + '@atproto/common-web': 0.4.16 1821 + '@atproto/syntax': 0.4.3 1822 + iso-datestring-validator: 2.2.2 1823 + multiformats: 9.9.0 1824 + zod: 3.25.76 1825 + 1826 + '@atproto/lexicon@0.6.1': 1827 + dependencies: 1828 + '@atproto/common-web': 0.4.16 1829 + '@atproto/syntax': 0.4.3 1830 + iso-datestring-validator: 2.2.2 1831 + multiformats: 9.9.0 1832 + zod: 3.25.76 1833 + 1834 + '@atproto/repo@0.8.12': 1835 + dependencies: 1836 + '@atproto/common': 0.5.11 1837 + '@atproto/common-web': 0.4.16 1838 + '@atproto/crypto': 0.4.5 1839 + '@atproto/lexicon': 0.6.1 1840 + '@ipld/dag-cbor': 7.0.3 1841 + multiformats: 9.9.0 1842 + uint8arrays: 3.0.0 1843 + varint: 6.0.0 1844 + zod: 3.25.76 1845 + 1846 + '@atproto/syntax@0.4.3': 1847 + dependencies: 1848 + tslib: 2.8.1 1849 + 1850 + '@atproto/xrpc@0.7.7': 1851 + dependencies: 1852 + '@atproto/lexicon': 0.6.1 1853 + zod: 3.25.76 1854 + 1855 + '@badrap/valita@0.4.6': {} 1856 + 1857 + '@esbuild/aix-ppc64@0.27.3': 1858 + optional: true 1859 + 1860 + '@esbuild/android-arm64@0.27.3': 1861 + optional: true 1862 + 1863 + '@esbuild/android-arm@0.27.3': 1864 + optional: true 1865 + 1866 + '@esbuild/android-x64@0.27.3': 1867 + optional: true 1868 + 1869 + '@esbuild/darwin-arm64@0.27.3': 1870 + optional: true 1871 + 1872 + '@esbuild/darwin-x64@0.27.3': 1873 + optional: true 1874 + 1875 + '@esbuild/freebsd-arm64@0.27.3': 1876 + optional: true 1877 + 1878 + '@esbuild/freebsd-x64@0.27.3': 1879 + optional: true 1880 + 1881 + '@esbuild/linux-arm64@0.27.3': 1882 + optional: true 1883 + 1884 + '@esbuild/linux-arm@0.27.3': 1885 + optional: true 1886 + 1887 + '@esbuild/linux-ia32@0.27.3': 1888 + optional: true 1889 + 1890 + '@esbuild/linux-loong64@0.27.3': 1891 + optional: true 1892 + 1893 + '@esbuild/linux-mips64el@0.27.3': 1894 + optional: true 1895 + 1896 + '@esbuild/linux-ppc64@0.27.3': 1897 + optional: true 1898 + 1899 + '@esbuild/linux-riscv64@0.27.3': 1900 + optional: true 1901 + 1902 + '@esbuild/linux-s390x@0.27.3': 1903 + optional: true 1904 + 1905 + '@esbuild/linux-x64@0.27.3': 1906 + optional: true 1907 + 1908 + '@esbuild/netbsd-arm64@0.27.3': 1909 + optional: true 1910 + 1911 + '@esbuild/netbsd-x64@0.27.3': 1912 + optional: true 1913 + 1914 + '@esbuild/openbsd-arm64@0.27.3': 1915 + optional: true 1916 + 1917 + '@esbuild/openbsd-x64@0.27.3': 1918 + optional: true 1919 + 1920 + '@esbuild/openharmony-arm64@0.27.3': 1921 + optional: true 1922 + 1923 + '@esbuild/sunos-x64@0.27.3': 1924 + optional: true 1925 + 1926 + '@esbuild/win32-arm64@0.27.3': 1927 + optional: true 1928 + 1929 + '@esbuild/win32-ia32@0.27.3': 1930 + optional: true 1931 + 1932 + '@esbuild/win32-x64@0.27.3': 1933 + optional: true 1934 + 1935 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': 1936 + dependencies: 1937 + eslint: 9.39.2 1938 + eslint-visitor-keys: 3.4.3 1939 + 1940 + '@eslint-community/regexpp@4.12.2': {} 1941 + 1942 + '@eslint/config-array@0.21.1': 1943 + dependencies: 1944 + '@eslint/object-schema': 2.1.7 1945 + debug: 4.4.3 1946 + minimatch: 3.1.2 1947 + transitivePeerDependencies: 1948 + - supports-color 1949 + 1950 + '@eslint/config-helpers@0.4.2': 1951 + dependencies: 1952 + '@eslint/core': 0.17.0 1953 + 1954 + '@eslint/core@0.17.0': 1955 + dependencies: 1956 + '@types/json-schema': 7.0.15 1957 + 1958 + '@eslint/eslintrc@3.3.3': 1959 + dependencies: 1960 + ajv: 6.12.6 1961 + debug: 4.4.3 1962 + espree: 10.4.0 1963 + globals: 14.0.0 1964 + ignore: 5.3.2 1965 + import-fresh: 3.3.1 1966 + js-yaml: 4.1.1 1967 + minimatch: 3.1.2 1968 + strip-json-comments: 3.1.1 1969 + transitivePeerDependencies: 1970 + - supports-color 1971 + 1972 + '@eslint/js@9.39.2': {} 1973 + 1974 + '@eslint/object-schema@2.1.7': {} 1975 + 1976 + '@eslint/plugin-kit@0.4.1': 1977 + dependencies: 1978 + '@eslint/core': 0.17.0 1979 + levn: 0.4.1 1980 + 1981 + '@humanfs/core@0.19.1': {} 1982 + 1983 + '@humanfs/node@0.16.7': 1984 + dependencies: 1985 + '@humanfs/core': 0.19.1 1986 + '@humanwhocodes/retry': 0.4.3 1987 + 1988 + '@humanwhocodes/module-importer@1.0.1': {} 1989 + 1990 + '@humanwhocodes/retry@0.4.3': {} 1991 + 1992 + '@ipld/dag-cbor@7.0.3': 1993 + dependencies: 1994 + cborg: 1.10.2 1995 + multiformats: 9.9.0 1996 + 1997 + '@noble/curves@1.9.7': 1998 + dependencies: 1999 + '@noble/hashes': 1.8.0 2000 + 2001 + '@noble/hashes@1.8.0': {} 2002 + 2003 + '@noble/secp256k1@3.0.0': {} 2004 + 2005 + '@pds-moover/lexicons@1.0.1': 2006 + dependencies: 2007 + '@atproto/lexicon': 0.5.2 2008 + '@atproto/xrpc': 0.7.7 2009 + 2010 + '@rollup/rollup-android-arm-eabi@4.57.1': 2011 + optional: true 2012 + 2013 + '@rollup/rollup-android-arm64@4.57.1': 2014 + optional: true 2015 + 2016 + '@rollup/rollup-darwin-arm64@4.57.1': 2017 + optional: true 2018 + 2019 + '@rollup/rollup-darwin-x64@4.57.1': 2020 + optional: true 2021 + 2022 + '@rollup/rollup-freebsd-arm64@4.57.1': 2023 + optional: true 2024 + 2025 + '@rollup/rollup-freebsd-x64@4.57.1': 2026 + optional: true 2027 + 2028 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 2029 + optional: true 2030 + 2031 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 2032 + optional: true 2033 + 2034 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 2035 + optional: true 2036 + 2037 + '@rollup/rollup-linux-arm64-musl@4.57.1': 2038 + optional: true 2039 + 2040 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 2041 + optional: true 2042 + 2043 + '@rollup/rollup-linux-loong64-musl@4.57.1': 2044 + optional: true 2045 + 2046 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 2047 + optional: true 2048 + 2049 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 2050 + optional: true 2051 + 2052 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 2053 + optional: true 2054 + 2055 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 2056 + optional: true 2057 + 2058 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 2059 + optional: true 2060 + 2061 + '@rollup/rollup-linux-x64-gnu@4.57.1': 2062 + optional: true 2063 + 2064 + '@rollup/rollup-linux-x64-musl@4.57.1': 2065 + optional: true 2066 + 2067 + '@rollup/rollup-openbsd-x64@4.57.1': 2068 + optional: true 2069 + 2070 + '@rollup/rollup-openharmony-arm64@4.57.1': 2071 + optional: true 2072 + 2073 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 2074 + optional: true 2075 + 2076 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 2077 + optional: true 2078 + 2079 + '@rollup/rollup-win32-x64-gnu@4.57.1': 2080 + optional: true 2081 + 2082 + '@rollup/rollup-win32-x64-msvc@4.57.1': 2083 + optional: true 2084 + 2085 + '@rtsao/scc@1.1.0': {} 2086 + 2087 + '@standard-schema/spec@1.1.0': {} 2088 + 2089 + '@ts-morph/common@0.28.1': 2090 + dependencies: 2091 + minimatch: 10.2.1 2092 + path-browserify: 1.0.1 2093 + tinyglobby: 0.2.15 2094 + 2095 + '@types/estree@1.0.8': {} 2096 + 2097 + '@types/json-schema@7.0.15': {} 2098 + 2099 + '@types/json5@0.0.29': {} 2100 + 2101 + '@vue/reactivity@3.1.5': 2102 + dependencies: 2103 + '@vue/shared': 3.1.5 2104 + 2105 + '@vue/shared@3.1.5': {} 2106 + 2107 + abort-controller@3.0.0: 2108 + dependencies: 2109 + event-target-shim: 5.0.1 2110 + 2111 + acorn-jsx@5.3.2(acorn@8.15.0): 2112 + dependencies: 2113 + acorn: 8.15.0 2114 + 2115 + acorn@8.15.0: {} 2116 + 2117 + ajv@6.12.6: 2118 + dependencies: 2119 + fast-deep-equal: 3.1.3 2120 + fast-json-stable-stringify: 2.1.0 2121 + json-schema-traverse: 0.4.1 2122 + uri-js: 4.4.1 2123 + 2124 + alpinejs@3.15.8: 2125 + dependencies: 2126 + '@vue/reactivity': 3.1.5 2127 + 2128 + ansi-regex@5.0.1: {} 2129 + 2130 + ansi-styles@4.3.0: 2131 + dependencies: 2132 + color-convert: 2.0.1 2133 + 2134 + argparse@2.0.1: {} 2135 + 2136 + array-buffer-byte-length@1.0.2: 2137 + dependencies: 2138 + call-bound: 1.0.4 2139 + is-array-buffer: 3.0.5 2140 + 2141 + array-includes@3.1.9: 2142 + dependencies: 2143 + call-bind: 1.0.8 2144 + call-bound: 1.0.4 2145 + define-properties: 1.2.1 2146 + es-abstract: 1.24.1 2147 + es-object-atoms: 1.1.1 2148 + get-intrinsic: 1.3.0 2149 + is-string: 1.1.1 2150 + math-intrinsics: 1.1.0 2151 + 2152 + array.prototype.findlastindex@1.2.6: 2153 + dependencies: 2154 + call-bind: 1.0.8 2155 + call-bound: 1.0.4 2156 + define-properties: 1.2.1 2157 + es-abstract: 1.24.1 2158 + es-errors: 1.3.0 2159 + es-object-atoms: 1.1.1 2160 + es-shim-unscopables: 1.1.0 2161 + 2162 + array.prototype.flat@1.3.3: 2163 + dependencies: 2164 + call-bind: 1.0.8 2165 + define-properties: 1.2.1 2166 + es-abstract: 1.24.1 2167 + es-shim-unscopables: 1.1.0 2168 + 2169 + array.prototype.flatmap@1.3.3: 2170 + dependencies: 2171 + call-bind: 1.0.8 2172 + define-properties: 1.2.1 2173 + es-abstract: 1.24.1 2174 + es-shim-unscopables: 1.1.0 2175 + 2176 + arraybuffer.prototype.slice@1.0.4: 2177 + dependencies: 2178 + array-buffer-byte-length: 1.0.2 2179 + call-bind: 1.0.8 2180 + define-properties: 1.2.1 2181 + es-abstract: 1.24.1 2182 + es-errors: 1.3.0 2183 + get-intrinsic: 1.3.0 2184 + is-array-buffer: 3.0.5 2185 + 2186 + async-function@1.0.0: {} 2187 + 2188 + atomic-sleep@1.0.0: {} 2189 + 2190 + available-typed-arrays@1.0.7: 2191 + dependencies: 2192 + possible-typed-array-names: 1.1.0 2193 + 2194 + await-lock@2.2.2: {} 2195 + 2196 + balanced-match@1.0.2: {} 2197 + 2198 + balanced-match@4.0.3: {} 2199 + 2200 + base64-js@1.5.1: {} 2201 + 2202 + brace-expansion@1.1.12: 2203 + dependencies: 2204 + balanced-match: 1.0.2 2205 + concat-map: 0.0.1 2206 + 2207 + brace-expansion@5.0.2: 2208 + dependencies: 2209 + balanced-match: 4.0.3 2210 + 2211 + buffer@6.0.3: 2212 + dependencies: 2213 + base64-js: 1.5.1 2214 + ieee754: 1.2.1 2215 + 2216 + call-bind-apply-helpers@1.0.2: 2217 + dependencies: 2218 + es-errors: 1.3.0 2219 + function-bind: 1.1.2 2220 + 2221 + call-bind@1.0.8: 2222 + dependencies: 2223 + call-bind-apply-helpers: 1.0.2 2224 + es-define-property: 1.0.1 2225 + get-intrinsic: 1.3.0 2226 + set-function-length: 1.2.2 2227 + 2228 + call-bound@1.0.4: 2229 + dependencies: 2230 + call-bind-apply-helpers: 1.0.2 2231 + get-intrinsic: 1.3.0 2232 + 2233 + callsites@3.1.0: {} 2234 + 2235 + cborg@1.10.2: {} 2236 + 2237 + chalk@4.1.2: 2238 + dependencies: 2239 + ansi-styles: 4.3.0 2240 + supports-color: 7.2.0 2241 + 2242 + cliui@8.0.1: 2243 + dependencies: 2244 + string-width: 4.2.3 2245 + strip-ansi: 6.0.1 2246 + wrap-ansi: 7.0.0 2247 + 2248 + code-block-writer@13.0.3: {} 2249 + 2250 + color-convert@2.0.1: 2251 + dependencies: 2252 + color-name: 1.1.4 2253 + 2254 + color-name@1.1.4: {} 2255 + 2256 + concat-map@0.0.1: {} 2257 + 2258 + core-js@3.48.0: {} 2259 + 2260 + cross-spawn@7.0.6: 2261 + dependencies: 2262 + path-key: 3.1.1 2263 + shebang-command: 2.0.0 2264 + which: 2.0.2 2265 + 2266 + data-view-buffer@1.0.2: 2267 + dependencies: 2268 + call-bound: 1.0.4 2269 + es-errors: 1.3.0 2270 + is-data-view: 1.0.2 2271 + 2272 + data-view-byte-length@1.0.2: 2273 + dependencies: 2274 + call-bound: 1.0.4 2275 + es-errors: 1.3.0 2276 + is-data-view: 1.0.2 2277 + 2278 + data-view-byte-offset@1.0.1: 2279 + dependencies: 2280 + call-bound: 1.0.4 2281 + es-errors: 1.3.0 2282 + is-data-view: 1.0.2 2283 + 2284 + debug@3.2.7: 2285 + dependencies: 2286 + ms: 2.1.3 2287 + 2288 + debug@4.4.3: 2289 + dependencies: 2290 + ms: 2.1.3 2291 + 2292 + deep-is@0.1.4: {} 2293 + 2294 + define-data-property@1.1.4: 2295 + dependencies: 2296 + es-define-property: 1.0.1 2297 + es-errors: 1.3.0 2298 + gopd: 1.2.0 2299 + 2300 + define-properties@1.2.1: 2301 + dependencies: 2302 + define-data-property: 1.1.4 2303 + has-property-descriptors: 1.0.2 2304 + object-keys: 1.1.1 2305 + 2306 + doctrine@2.1.0: 2307 + dependencies: 2308 + esutils: 2.0.3 2309 + 2310 + dunder-proto@1.0.1: 2311 + dependencies: 2312 + call-bind-apply-helpers: 1.0.2 2313 + es-errors: 1.3.0 2314 + gopd: 1.2.0 2315 + 2316 + emoji-regex@8.0.0: {} 2317 + 2318 + es-abstract@1.24.1: 2319 + dependencies: 2320 + array-buffer-byte-length: 1.0.2 2321 + arraybuffer.prototype.slice: 1.0.4 2322 + available-typed-arrays: 1.0.7 2323 + call-bind: 1.0.8 2324 + call-bound: 1.0.4 2325 + data-view-buffer: 1.0.2 2326 + data-view-byte-length: 1.0.2 2327 + data-view-byte-offset: 1.0.1 2328 + es-define-property: 1.0.1 2329 + es-errors: 1.3.0 2330 + es-object-atoms: 1.1.1 2331 + es-set-tostringtag: 2.1.0 2332 + es-to-primitive: 1.3.0 2333 + function.prototype.name: 1.1.8 2334 + get-intrinsic: 1.3.0 2335 + get-proto: 1.0.1 2336 + get-symbol-description: 1.1.0 2337 + globalthis: 1.0.4 2338 + gopd: 1.2.0 2339 + has-property-descriptors: 1.0.2 2340 + has-proto: 1.2.0 2341 + has-symbols: 1.1.0 2342 + hasown: 2.0.2 2343 + internal-slot: 1.1.0 2344 + is-array-buffer: 3.0.5 2345 + is-callable: 1.2.7 2346 + is-data-view: 1.0.2 2347 + is-negative-zero: 2.0.3 2348 + is-regex: 1.2.1 2349 + is-set: 2.0.3 2350 + is-shared-array-buffer: 1.0.4 2351 + is-string: 1.1.1 2352 + is-typed-array: 1.1.15 2353 + is-weakref: 1.1.1 2354 + math-intrinsics: 1.1.0 2355 + object-inspect: 1.13.4 2356 + object-keys: 1.1.1 2357 + object.assign: 4.1.7 2358 + own-keys: 1.0.1 2359 + regexp.prototype.flags: 1.5.4 2360 + safe-array-concat: 1.1.3 2361 + safe-push-apply: 1.0.0 2362 + safe-regex-test: 1.1.0 2363 + set-proto: 1.0.0 2364 + stop-iteration-iterator: 1.1.0 2365 + string.prototype.trim: 1.2.10 2366 + string.prototype.trimend: 1.0.9 2367 + string.prototype.trimstart: 1.0.8 2368 + typed-array-buffer: 1.0.3 2369 + typed-array-byte-length: 1.0.3 2370 + typed-array-byte-offset: 1.0.4 2371 + typed-array-length: 1.0.7 2372 + unbox-primitive: 1.1.0 2373 + which-typed-array: 1.1.20 2374 + 2375 + es-define-property@1.0.1: {} 2376 + 2377 + es-errors@1.3.0: {} 2378 + 2379 + es-object-atoms@1.1.1: 2380 + dependencies: 2381 + es-errors: 1.3.0 2382 + 2383 + es-set-tostringtag@2.1.0: 2384 + dependencies: 2385 + es-errors: 1.3.0 2386 + get-intrinsic: 1.3.0 2387 + has-tostringtag: 1.0.2 2388 + hasown: 2.0.2 2389 + 2390 + es-shim-unscopables@1.1.0: 2391 + dependencies: 2392 + hasown: 2.0.2 2393 + 2394 + es-to-primitive@1.3.0: 2395 + dependencies: 2396 + is-callable: 1.2.7 2397 + is-date-object: 1.1.0 2398 + is-symbol: 1.1.1 2399 + 2400 + esbuild@0.27.3: 2401 + optionalDependencies: 2402 + '@esbuild/aix-ppc64': 0.27.3 2403 + '@esbuild/android-arm': 0.27.3 2404 + '@esbuild/android-arm64': 0.27.3 2405 + '@esbuild/android-x64': 0.27.3 2406 + '@esbuild/darwin-arm64': 0.27.3 2407 + '@esbuild/darwin-x64': 0.27.3 2408 + '@esbuild/freebsd-arm64': 0.27.3 2409 + '@esbuild/freebsd-x64': 0.27.3 2410 + '@esbuild/linux-arm': 0.27.3 2411 + '@esbuild/linux-arm64': 0.27.3 2412 + '@esbuild/linux-ia32': 0.27.3 2413 + '@esbuild/linux-loong64': 0.27.3 2414 + '@esbuild/linux-mips64el': 0.27.3 2415 + '@esbuild/linux-ppc64': 0.27.3 2416 + '@esbuild/linux-riscv64': 0.27.3 2417 + '@esbuild/linux-s390x': 0.27.3 2418 + '@esbuild/linux-x64': 0.27.3 2419 + '@esbuild/netbsd-arm64': 0.27.3 2420 + '@esbuild/netbsd-x64': 0.27.3 2421 + '@esbuild/openbsd-arm64': 0.27.3 2422 + '@esbuild/openbsd-x64': 0.27.3 2423 + '@esbuild/openharmony-arm64': 0.27.3 2424 + '@esbuild/sunos-x64': 0.27.3 2425 + '@esbuild/win32-arm64': 0.27.3 2426 + '@esbuild/win32-ia32': 0.27.3 2427 + '@esbuild/win32-x64': 0.27.3 2428 + 2429 + escalade@3.2.0: {} 2430 + 2431 + escape-string-regexp@4.0.0: {} 2432 + 2433 + eslint-import-resolver-node@0.3.9: 2434 + dependencies: 2435 + debug: 3.2.7 2436 + is-core-module: 2.16.1 2437 + resolve: 1.22.11 2438 + transitivePeerDependencies: 2439 + - supports-color 2440 + 2441 + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.39.2): 2442 + dependencies: 2443 + debug: 3.2.7 2444 + optionalDependencies: 2445 + eslint: 9.39.2 2446 + eslint-import-resolver-node: 0.3.9 2447 + transitivePeerDependencies: 2448 + - supports-color 2449 + 2450 + eslint-plugin-import@2.32.0(eslint@9.39.2): 2451 + dependencies: 2452 + '@rtsao/scc': 1.1.0 2453 + array-includes: 3.1.9 2454 + array.prototype.findlastindex: 1.2.6 2455 + array.prototype.flat: 1.3.3 2456 + array.prototype.flatmap: 1.3.3 2457 + debug: 3.2.7 2458 + doctrine: 2.1.0 2459 + eslint: 9.39.2 2460 + eslint-import-resolver-node: 0.3.9 2461 + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.39.2) 2462 + hasown: 2.0.2 2463 + is-core-module: 2.16.1 2464 + is-glob: 4.0.3 2465 + minimatch: 3.1.2 2466 + object.fromentries: 2.0.8 2467 + object.groupby: 1.0.3 2468 + object.values: 1.2.1 2469 + semver: 6.3.1 2470 + string.prototype.trimend: 1.0.9 2471 + tsconfig-paths: 3.15.0 2472 + transitivePeerDependencies: 2473 + - eslint-import-resolver-typescript 2474 + - eslint-import-resolver-webpack 2475 + - supports-color 2476 + 2477 + eslint-scope@8.4.0: 2478 + dependencies: 2479 + esrecurse: 4.3.0 2480 + estraverse: 5.3.0 2481 + 2482 + eslint-visitor-keys@3.4.3: {} 2483 + 2484 + eslint-visitor-keys@4.2.1: {} 2485 + 2486 + eslint@9.39.2: 2487 + dependencies: 2488 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) 2489 + '@eslint-community/regexpp': 4.12.2 2490 + '@eslint/config-array': 0.21.1 2491 + '@eslint/config-helpers': 0.4.2 2492 + '@eslint/core': 0.17.0 2493 + '@eslint/eslintrc': 3.3.3 2494 + '@eslint/js': 9.39.2 2495 + '@eslint/plugin-kit': 0.4.1 2496 + '@humanfs/node': 0.16.7 2497 + '@humanwhocodes/module-importer': 1.0.1 2498 + '@humanwhocodes/retry': 0.4.3 2499 + '@types/estree': 1.0.8 2500 + ajv: 6.12.6 2501 + chalk: 4.1.2 2502 + cross-spawn: 7.0.6 2503 + debug: 4.4.3 2504 + escape-string-regexp: 4.0.0 2505 + eslint-scope: 8.4.0 2506 + eslint-visitor-keys: 4.2.1 2507 + espree: 10.4.0 2508 + esquery: 1.7.0 2509 + esutils: 2.0.3 2510 + fast-deep-equal: 3.1.3 2511 + file-entry-cache: 8.0.0 2512 + find-up: 5.0.0 2513 + glob-parent: 6.0.2 2514 + ignore: 5.3.2 2515 + imurmurhash: 0.1.4 2516 + is-glob: 4.0.3 2517 + json-stable-stringify-without-jsonify: 1.0.1 2518 + lodash.merge: 4.6.2 2519 + minimatch: 3.1.2 2520 + natural-compare: 1.4.0 2521 + optionator: 0.9.4 2522 + transitivePeerDependencies: 2523 + - supports-color 2524 + 2525 + esm-env@1.2.2: {} 2526 + 2527 + espree@10.4.0: 2528 + dependencies: 2529 + acorn: 8.15.0 2530 + acorn-jsx: 5.3.2(acorn@8.15.0) 2531 + eslint-visitor-keys: 4.2.1 2532 + 2533 + esquery@1.7.0: 2534 + dependencies: 2535 + estraverse: 5.3.0 2536 + 2537 + esrecurse@4.3.0: 2538 + dependencies: 2539 + estraverse: 5.3.0 2540 + 2541 + estraverse@5.3.0: {} 2542 + 2543 + esutils@2.0.3: {} 2544 + 2545 + event-target-shim@5.0.1: {} 2546 + 2547 + events@3.3.0: {} 2548 + 2549 + fast-deep-equal@3.1.3: {} 2550 + 2551 + fast-json-stable-stringify@2.1.0: {} 2552 + 2553 + fast-levenshtein@2.0.6: {} 2554 + 2555 + fast-redact@3.5.0: {} 2556 + 2557 + fdir@6.5.0(picomatch@4.0.3): 2558 + optionalDependencies: 2559 + picomatch: 4.0.3 2560 + 2561 + file-entry-cache@8.0.0: 2562 + dependencies: 2563 + flat-cache: 4.0.1 2564 + 2565 + find-up@5.0.0: 2566 + dependencies: 2567 + locate-path: 6.0.0 2568 + path-exists: 4.0.0 2569 + 2570 + flat-cache@4.0.1: 2571 + dependencies: 2572 + flatted: 3.3.3 2573 + keyv: 4.5.4 2574 + 2575 + flatted@3.3.3: {} 2576 + 2577 + for-each@0.3.5: 2578 + dependencies: 2579 + is-callable: 1.2.7 2580 + 2581 + fsevents@2.3.3: 2582 + optional: true 2583 + 2584 + function-bind@1.1.2: {} 2585 + 2586 + function.prototype.name@1.1.8: 2587 + dependencies: 2588 + call-bind: 1.0.8 2589 + call-bound: 1.0.4 2590 + define-properties: 1.2.1 2591 + functions-have-names: 1.2.3 2592 + hasown: 2.0.2 2593 + is-callable: 1.2.7 2594 + 2595 + functions-have-names@1.2.3: {} 2596 + 2597 + generator-function@2.0.1: {} 2598 + 2599 + get-caller-file@2.0.5: {} 2600 + 2601 + get-intrinsic@1.3.0: 2602 + dependencies: 2603 + call-bind-apply-helpers: 1.0.2 2604 + es-define-property: 1.0.1 2605 + es-errors: 1.3.0 2606 + es-object-atoms: 1.1.1 2607 + function-bind: 1.1.2 2608 + get-proto: 1.0.1 2609 + gopd: 1.2.0 2610 + has-symbols: 1.1.0 2611 + hasown: 2.0.2 2612 + math-intrinsics: 1.1.0 2613 + 2614 + get-proto@1.0.1: 2615 + dependencies: 2616 + dunder-proto: 1.0.1 2617 + es-object-atoms: 1.1.1 2618 + 2619 + get-symbol-description@1.1.0: 2620 + dependencies: 2621 + call-bound: 1.0.4 2622 + es-errors: 1.3.0 2623 + get-intrinsic: 1.3.0 2624 + 2625 + glob-parent@6.0.2: 2626 + dependencies: 2627 + is-glob: 4.0.3 2628 + 2629 + globals@14.0.0: {} 2630 + 2631 + globalthis@1.0.4: 2632 + dependencies: 2633 + define-properties: 1.2.1 2634 + gopd: 1.2.0 2635 + 2636 + gopd@1.2.0: {} 2637 + 2638 + has-bigints@1.1.0: {} 2639 + 2640 + has-flag@4.0.0: {} 2641 + 2642 + has-property-descriptors@1.0.2: 2643 + dependencies: 2644 + es-define-property: 1.0.1 2645 + 2646 + has-proto@1.2.0: 2647 + dependencies: 2648 + dunder-proto: 1.0.1 2649 + 2650 + has-symbols@1.1.0: {} 2651 + 2652 + has-tostringtag@1.0.2: 2653 + dependencies: 2654 + has-symbols: 1.1.0 2655 + 2656 + hasown@2.0.2: 2657 + dependencies: 2658 + function-bind: 1.1.2 2659 + 2660 + ieee754@1.2.1: {} 2661 + 2662 + ignore@5.3.2: {} 2663 + 2664 + import-fresh@3.3.1: 2665 + dependencies: 2666 + parent-module: 1.0.1 2667 + resolve-from: 4.0.0 2668 + 2669 + imurmurhash@0.1.4: {} 2670 + 2671 + internal-slot@1.1.0: 2672 + dependencies: 2673 + es-errors: 1.3.0 2674 + hasown: 2.0.2 2675 + side-channel: 1.1.0 2676 + 2677 + is-array-buffer@3.0.5: 2678 + dependencies: 2679 + call-bind: 1.0.8 2680 + call-bound: 1.0.4 2681 + get-intrinsic: 1.3.0 2682 + 2683 + is-async-function@2.1.1: 2684 + dependencies: 2685 + async-function: 1.0.0 2686 + call-bound: 1.0.4 2687 + get-proto: 1.0.1 2688 + has-tostringtag: 1.0.2 2689 + safe-regex-test: 1.1.0 2690 + 2691 + is-bigint@1.1.0: 2692 + dependencies: 2693 + has-bigints: 1.1.0 2694 + 2695 + is-boolean-object@1.2.2: 2696 + dependencies: 2697 + call-bound: 1.0.4 2698 + has-tostringtag: 1.0.2 2699 + 2700 + is-callable@1.2.7: {} 2701 + 2702 + is-core-module@2.16.1: 2703 + dependencies: 2704 + hasown: 2.0.2 2705 + 2706 + is-data-view@1.0.2: 2707 + dependencies: 2708 + call-bound: 1.0.4 2709 + get-intrinsic: 1.3.0 2710 + is-typed-array: 1.1.15 2711 + 2712 + is-date-object@1.1.0: 2713 + dependencies: 2714 + call-bound: 1.0.4 2715 + has-tostringtag: 1.0.2 2716 + 2717 + is-extglob@2.1.1: {} 2718 + 2719 + is-finalizationregistry@1.1.1: 2720 + dependencies: 2721 + call-bound: 1.0.4 2722 + 2723 + is-fullwidth-code-point@3.0.0: {} 2724 + 2725 + is-generator-function@1.1.2: 2726 + dependencies: 2727 + call-bound: 1.0.4 2728 + generator-function: 2.0.1 2729 + get-proto: 1.0.1 2730 + has-tostringtag: 1.0.2 2731 + safe-regex-test: 1.1.0 2732 + 2733 + is-glob@4.0.3: 2734 + dependencies: 2735 + is-extglob: 2.1.1 2736 + 2737 + is-map@2.0.3: {} 2738 + 2739 + is-negative-zero@2.0.3: {} 2740 + 2741 + is-number-object@1.1.1: 2742 + dependencies: 2743 + call-bound: 1.0.4 2744 + has-tostringtag: 1.0.2 2745 + 2746 + is-regex@1.2.1: 2747 + dependencies: 2748 + call-bound: 1.0.4 2749 + gopd: 1.2.0 2750 + has-tostringtag: 1.0.2 2751 + hasown: 2.0.2 2752 + 2753 + is-set@2.0.3: {} 2754 + 2755 + is-shared-array-buffer@1.0.4: 2756 + dependencies: 2757 + call-bound: 1.0.4 2758 + 2759 + is-string@1.1.1: 2760 + dependencies: 2761 + call-bound: 1.0.4 2762 + has-tostringtag: 1.0.2 2763 + 2764 + is-symbol@1.1.1: 2765 + dependencies: 2766 + call-bound: 1.0.4 2767 + has-symbols: 1.1.0 2768 + safe-regex-test: 1.1.0 2769 + 2770 + is-typed-array@1.1.15: 2771 + dependencies: 2772 + which-typed-array: 1.1.20 2773 + 2774 + is-weakmap@2.0.2: {} 2775 + 2776 + is-weakref@1.1.1: 2777 + dependencies: 2778 + call-bound: 1.0.4 2779 + 2780 + is-weakset@2.0.4: 2781 + dependencies: 2782 + call-bound: 1.0.4 2783 + get-intrinsic: 1.3.0 2784 + 2785 + isarray@2.0.5: {} 2786 + 2787 + isexe@2.0.0: {} 2788 + 2789 + iso-datestring-validator@2.2.2: {} 2790 + 2791 + js-yaml@4.1.1: 2792 + dependencies: 2793 + argparse: 2.0.1 2794 + 2795 + json-buffer@3.0.1: {} 2796 + 2797 + json-schema-traverse@0.4.1: {} 2798 + 2799 + json-stable-stringify-without-jsonify@1.0.1: {} 2800 + 2801 + json5@1.0.2: 2802 + dependencies: 2803 + minimist: 1.2.8 2804 + 2805 + keyv@4.5.4: 2806 + dependencies: 2807 + json-buffer: 3.0.1 2808 + 2809 + levn@0.4.1: 2810 + dependencies: 2811 + prelude-ls: 1.2.1 2812 + type-check: 0.4.0 2813 + 2814 + locate-path@6.0.0: 2815 + dependencies: 2816 + p-locate: 5.0.0 2817 + 2818 + lodash.merge@4.6.2: {} 2819 + 2820 + lru-cache@10.4.3: {} 2821 + 2822 + math-intrinsics@1.1.0: {} 2823 + 2824 + minimatch@10.2.1: 2825 + dependencies: 2826 + brace-expansion: 5.0.2 2827 + 2828 + minimatch@3.1.2: 2829 + dependencies: 2830 + brace-expansion: 1.1.12 2831 + 2832 + minimist@1.2.8: {} 2833 + 2834 + ms@2.1.3: {} 2835 + 2836 + multiformats@9.9.0: {} 2837 + 2838 + nanoid@3.3.11: {} 2839 + 2840 + natural-compare@1.4.0: {} 2841 + 2842 + object-inspect@1.13.4: {} 2843 + 2844 + object-keys@1.1.1: {} 2845 + 2846 + object.assign@4.1.7: 2847 + dependencies: 2848 + call-bind: 1.0.8 2849 + call-bound: 1.0.4 2850 + define-properties: 1.2.1 2851 + es-object-atoms: 1.1.1 2852 + has-symbols: 1.1.0 2853 + object-keys: 1.1.1 2854 + 2855 + object.fromentries@2.0.8: 2856 + dependencies: 2857 + call-bind: 1.0.8 2858 + define-properties: 1.2.1 2859 + es-abstract: 1.24.1 2860 + es-object-atoms: 1.1.1 2861 + 2862 + object.groupby@1.0.3: 2863 + dependencies: 2864 + call-bind: 1.0.8 2865 + define-properties: 1.2.1 2866 + es-abstract: 1.24.1 2867 + 2868 + object.values@1.2.1: 2869 + dependencies: 2870 + call-bind: 1.0.8 2871 + call-bound: 1.0.4 2872 + define-properties: 1.2.1 2873 + es-object-atoms: 1.1.1 2874 + 2875 + on-exit-leak-free@2.1.2: {} 2876 + 2877 + optionator@0.9.4: 2878 + dependencies: 2879 + deep-is: 0.1.4 2880 + fast-levenshtein: 2.0.6 2881 + levn: 0.4.1 2882 + prelude-ls: 1.2.1 2883 + type-check: 0.4.0 2884 + word-wrap: 1.2.5 2885 + 2886 + own-keys@1.0.1: 2887 + dependencies: 2888 + get-intrinsic: 1.3.0 2889 + object-keys: 1.1.1 2890 + safe-push-apply: 1.0.0 2891 + 2892 + p-limit@3.1.0: 2893 + dependencies: 2894 + yocto-queue: 0.1.0 2895 + 2896 + p-locate@5.0.0: 2897 + dependencies: 2898 + p-limit: 3.1.0 2899 + 2900 + parent-module@1.0.1: 2901 + dependencies: 2902 + callsites: 3.1.0 2903 + 2904 + path-browserify@1.0.1: {} 2905 + 2906 + path-exists@4.0.0: {} 2907 + 2908 + path-key@3.1.1: {} 2909 + 2910 + path-parse@1.0.7: {} 2911 + 2912 + picocolors@1.1.1: {} 2913 + 2914 + picomatch@2.3.1: {} 2915 + 2916 + picomatch@4.0.3: {} 2917 + 2918 + pino-abstract-transport@1.2.0: 2919 + dependencies: 2920 + readable-stream: 4.7.0 2921 + split2: 4.2.0 2922 + 2923 + pino-std-serializers@6.2.2: {} 2924 + 2925 + pino@8.21.0: 2926 + dependencies: 2927 + atomic-sleep: 1.0.0 2928 + fast-redact: 3.5.0 2929 + on-exit-leak-free: 2.1.2 2930 + pino-abstract-transport: 1.2.0 2931 + pino-std-serializers: 6.2.2 2932 + process-warning: 3.0.0 2933 + quick-format-unescaped: 4.0.4 2934 + real-require: 0.2.0 2935 + safe-stable-stringify: 2.5.0 2936 + sonic-boom: 3.8.1 2937 + thread-stream: 2.7.0 2938 + 2939 + possible-typed-array-names@1.1.0: {} 2940 + 2941 + postcss@8.5.6: 2942 + dependencies: 2943 + nanoid: 3.3.11 2944 + picocolors: 1.1.1 2945 + source-map-js: 1.2.1 2946 + 2947 + prelude-ls@1.2.1: {} 2948 + 2949 + prettier@3.8.1: {} 2950 + 2951 + process-warning@3.0.0: {} 2952 + 2953 + process@0.11.10: {} 2954 + 2955 + punycode@2.3.1: {} 2956 + 2957 + quick-format-unescaped@4.0.4: {} 2958 + 2959 + readable-stream@4.7.0: 2960 + dependencies: 2961 + abort-controller: 3.0.0 2962 + buffer: 6.0.3 2963 + events: 3.3.0 2964 + process: 0.11.10 2965 + string_decoder: 1.3.0 2966 + 2967 + real-require@0.2.0: {} 2968 + 2969 + reflect.getprototypeof@1.0.10: 2970 + dependencies: 2971 + call-bind: 1.0.8 2972 + define-properties: 1.2.1 2973 + es-abstract: 1.24.1 2974 + es-errors: 1.3.0 2975 + es-object-atoms: 1.1.1 2976 + get-intrinsic: 1.3.0 2977 + get-proto: 1.0.1 2978 + which-builtin-type: 1.2.1 2979 + 2980 + regexp.prototype.flags@1.5.4: 2981 + dependencies: 2982 + call-bind: 1.0.8 2983 + define-properties: 1.2.1 2984 + es-errors: 1.3.0 2985 + get-proto: 1.0.1 2986 + gopd: 1.2.0 2987 + set-function-name: 2.0.2 2988 + 2989 + require-directory@2.1.1: {} 2990 + 2991 + resolve-from@4.0.0: {} 2992 + 2993 + resolve@1.22.11: 2994 + dependencies: 2995 + is-core-module: 2.16.1 2996 + path-parse: 1.0.7 2997 + supports-preserve-symlinks-flag: 1.0.0 2998 + 2999 + rollup@4.57.1: 3000 + dependencies: 3001 + '@types/estree': 1.0.8 3002 + optionalDependencies: 3003 + '@rollup/rollup-android-arm-eabi': 4.57.1 3004 + '@rollup/rollup-android-arm64': 4.57.1 3005 + '@rollup/rollup-darwin-arm64': 4.57.1 3006 + '@rollup/rollup-darwin-x64': 4.57.1 3007 + '@rollup/rollup-freebsd-arm64': 4.57.1 3008 + '@rollup/rollup-freebsd-x64': 4.57.1 3009 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 3010 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 3011 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 3012 + '@rollup/rollup-linux-arm64-musl': 4.57.1 3013 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 3014 + '@rollup/rollup-linux-loong64-musl': 4.57.1 3015 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 3016 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 3017 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 3018 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 3019 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 3020 + '@rollup/rollup-linux-x64-gnu': 4.57.1 3021 + '@rollup/rollup-linux-x64-musl': 4.57.1 3022 + '@rollup/rollup-openbsd-x64': 4.57.1 3023 + '@rollup/rollup-openharmony-arm64': 4.57.1 3024 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 3025 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 3026 + '@rollup/rollup-win32-x64-gnu': 4.57.1 3027 + '@rollup/rollup-win32-x64-msvc': 4.57.1 3028 + fsevents: 2.3.3 3029 + 3030 + safe-array-concat@1.1.3: 3031 + dependencies: 3032 + call-bind: 1.0.8 3033 + call-bound: 1.0.4 3034 + get-intrinsic: 1.3.0 3035 + has-symbols: 1.1.0 3036 + isarray: 2.0.5 3037 + 3038 + safe-buffer@5.2.1: {} 3039 + 3040 + safe-push-apply@1.0.0: 3041 + dependencies: 3042 + es-errors: 1.3.0 3043 + isarray: 2.0.5 3044 + 3045 + safe-regex-test@1.1.0: 3046 + dependencies: 3047 + call-bound: 1.0.4 3048 + es-errors: 1.3.0 3049 + is-regex: 1.2.1 3050 + 3051 + safe-stable-stringify@2.5.0: {} 3052 + 3053 + semver@6.3.1: {} 3054 + 3055 + set-function-length@1.2.2: 3056 + dependencies: 3057 + define-data-property: 1.1.4 3058 + es-errors: 1.3.0 3059 + function-bind: 1.1.2 3060 + get-intrinsic: 1.3.0 3061 + gopd: 1.2.0 3062 + has-property-descriptors: 1.0.2 3063 + 3064 + set-function-name@2.0.2: 3065 + dependencies: 3066 + define-data-property: 1.1.4 3067 + es-errors: 1.3.0 3068 + functions-have-names: 1.2.3 3069 + has-property-descriptors: 1.0.2 3070 + 3071 + set-proto@1.0.0: 3072 + dependencies: 3073 + dunder-proto: 1.0.1 3074 + es-errors: 1.3.0 3075 + es-object-atoms: 1.1.1 3076 + 3077 + shebang-command@2.0.0: 3078 + dependencies: 3079 + shebang-regex: 3.0.0 3080 + 3081 + shebang-regex@3.0.0: {} 3082 + 3083 + side-channel-list@1.0.0: 3084 + dependencies: 3085 + es-errors: 1.3.0 3086 + object-inspect: 1.13.4 3087 + 3088 + side-channel-map@1.0.1: 3089 + dependencies: 3090 + call-bound: 1.0.4 3091 + es-errors: 1.3.0 3092 + get-intrinsic: 1.3.0 3093 + object-inspect: 1.13.4 3094 + 3095 + side-channel-weakmap@1.0.2: 3096 + dependencies: 3097 + call-bound: 1.0.4 3098 + es-errors: 1.3.0 3099 + get-intrinsic: 1.3.0 3100 + object-inspect: 1.13.4 3101 + side-channel-map: 1.0.1 3102 + 3103 + side-channel@1.1.0: 3104 + dependencies: 3105 + es-errors: 1.3.0 3106 + object-inspect: 1.13.4 3107 + side-channel-list: 1.0.0 3108 + side-channel-map: 1.0.1 3109 + side-channel-weakmap: 1.0.2 3110 + 3111 + sonic-boom@3.8.1: 3112 + dependencies: 3113 + atomic-sleep: 1.0.0 3114 + 3115 + source-map-js@1.2.1: {} 3116 + 3117 + split2@4.2.0: {} 3118 + 3119 + stop-iteration-iterator@1.1.0: 3120 + dependencies: 3121 + es-errors: 1.3.0 3122 + internal-slot: 1.1.0 3123 + 3124 + string-width@4.2.3: 3125 + dependencies: 3126 + emoji-regex: 8.0.0 3127 + is-fullwidth-code-point: 3.0.0 3128 + strip-ansi: 6.0.1 3129 + 3130 + string.prototype.trim@1.2.10: 3131 + dependencies: 3132 + call-bind: 1.0.8 3133 + call-bound: 1.0.4 3134 + define-data-property: 1.1.4 3135 + define-properties: 1.2.1 3136 + es-abstract: 1.24.1 3137 + es-object-atoms: 1.1.1 3138 + has-property-descriptors: 1.0.2 3139 + 3140 + string.prototype.trimend@1.0.9: 3141 + dependencies: 3142 + call-bind: 1.0.8 3143 + call-bound: 1.0.4 3144 + define-properties: 1.2.1 3145 + es-object-atoms: 1.1.1 3146 + 3147 + string.prototype.trimstart@1.0.8: 3148 + dependencies: 3149 + call-bind: 1.0.8 3150 + define-properties: 1.2.1 3151 + es-object-atoms: 1.1.1 3152 + 3153 + string_decoder@1.3.0: 3154 + dependencies: 3155 + safe-buffer: 5.2.1 3156 + 3157 + strip-ansi@6.0.1: 3158 + dependencies: 3159 + ansi-regex: 5.0.1 3160 + 3161 + strip-bom@3.0.0: {} 3162 + 3163 + strip-json-comments@3.1.1: {} 3164 + 3165 + supports-color@7.2.0: 3166 + dependencies: 3167 + has-flag: 4.0.0 3168 + 3169 + supports-preserve-symlinks-flag@1.0.0: {} 3170 + 3171 + thread-stream@2.7.0: 3172 + dependencies: 3173 + real-require: 0.2.0 3174 + 3175 + tinyglobby@0.2.15: 3176 + dependencies: 3177 + fdir: 6.5.0(picomatch@4.0.3) 3178 + picomatch: 4.0.3 3179 + 3180 + tlds@1.261.0: {} 3181 + 3182 + ts-morph@27.0.2: 3183 + dependencies: 3184 + '@ts-morph/common': 0.28.1 3185 + code-block-writer: 13.0.3 3186 + 3187 + tsconfig-paths@3.15.0: 3188 + dependencies: 3189 + '@types/json5': 0.0.29 3190 + json5: 1.0.2 3191 + minimist: 1.2.8 3192 + strip-bom: 3.0.0 3193 + 3194 + tslib@2.8.1: {} 3195 + 3196 + type-check@0.4.0: 3197 + dependencies: 3198 + prelude-ls: 1.2.1 3199 + 3200 + typed-array-buffer@1.0.3: 3201 + dependencies: 3202 + call-bound: 1.0.4 3203 + es-errors: 1.3.0 3204 + is-typed-array: 1.1.15 3205 + 3206 + typed-array-byte-length@1.0.3: 3207 + dependencies: 3208 + call-bind: 1.0.8 3209 + for-each: 0.3.5 3210 + gopd: 1.2.0 3211 + has-proto: 1.2.0 3212 + is-typed-array: 1.1.15 3213 + 3214 + typed-array-byte-offset@1.0.4: 3215 + dependencies: 3216 + available-typed-arrays: 1.0.7 3217 + call-bind: 1.0.8 3218 + for-each: 0.3.5 3219 + gopd: 1.2.0 3220 + has-proto: 1.2.0 3221 + is-typed-array: 1.1.15 3222 + reflect.getprototypeof: 1.0.10 3223 + 3224 + typed-array-length@1.0.7: 3225 + dependencies: 3226 + call-bind: 1.0.8 3227 + for-each: 0.3.5 3228 + gopd: 1.2.0 3229 + is-typed-array: 1.1.15 3230 + possible-typed-array-names: 1.1.0 3231 + reflect.getprototypeof: 1.0.10 3232 + 3233 + uint8arrays@3.0.0: 3234 + dependencies: 3235 + multiformats: 9.9.0 3236 + 3237 + unbox-primitive@1.1.0: 3238 + dependencies: 3239 + call-bound: 1.0.4 3240 + has-bigints: 1.1.0 3241 + has-symbols: 1.1.0 3242 + which-boxed-primitive: 1.1.1 3243 + 3244 + unicode-segmenter@0.14.5: {} 3245 + 3246 + uri-js@4.4.1: 3247 + dependencies: 3248 + punycode: 2.3.1 3249 + 3250 + varint@6.0.0: {} 3251 + 3252 + vite-plugin-full-reload@1.2.0: 3253 + dependencies: 3254 + picocolors: 1.1.1 3255 + picomatch: 2.3.1 3256 + 3257 + vite-rs-plugin@1.0.1(vite@7.3.1): 3258 + dependencies: 3259 + vite: 7.3.1 3260 + vite-plugin-full-reload: 1.2.0 3261 + 3262 + vite@7.3.1: 3263 + dependencies: 3264 + esbuild: 0.27.3 3265 + fdir: 6.5.0(picomatch@4.0.3) 3266 + picomatch: 4.0.3 3267 + postcss: 8.5.6 3268 + rollup: 4.57.1 3269 + tinyglobby: 0.2.15 3270 + optionalDependencies: 3271 + fsevents: 2.3.3 3272 + 3273 + which-boxed-primitive@1.1.1: 3274 + dependencies: 3275 + is-bigint: 1.1.0 3276 + is-boolean-object: 1.2.2 3277 + is-number-object: 1.1.1 3278 + is-string: 1.1.1 3279 + is-symbol: 1.1.1 3280 + 3281 + which-builtin-type@1.2.1: 3282 + dependencies: 3283 + call-bound: 1.0.4 3284 + function.prototype.name: 1.1.8 3285 + has-tostringtag: 1.0.2 3286 + is-async-function: 2.1.1 3287 + is-date-object: 1.1.0 3288 + is-finalizationregistry: 1.1.1 3289 + is-generator-function: 1.1.2 3290 + is-regex: 1.2.1 3291 + is-weakref: 1.1.1 3292 + isarray: 2.0.5 3293 + which-boxed-primitive: 1.1.1 3294 + which-collection: 1.0.2 3295 + which-typed-array: 1.1.20 3296 + 3297 + which-collection@1.0.2: 3298 + dependencies: 3299 + is-map: 2.0.3 3300 + is-set: 2.0.3 3301 + is-weakmap: 2.0.2 3302 + is-weakset: 2.0.4 3303 + 3304 + which-typed-array@1.1.20: 3305 + dependencies: 3306 + available-typed-arrays: 1.0.7 3307 + call-bind: 1.0.8 3308 + call-bound: 1.0.4 3309 + for-each: 0.3.5 3310 + get-proto: 1.0.1 3311 + gopd: 1.2.0 3312 + has-tostringtag: 1.0.2 3313 + 3314 + which@2.0.2: 3315 + dependencies: 3316 + isexe: 2.0.0 3317 + 3318 + word-wrap@1.2.5: {} 3319 + 3320 + wrap-ansi@7.0.0: 3321 + dependencies: 3322 + ansi-styles: 4.3.0 3323 + string-width: 4.2.3 3324 + strip-ansi: 6.0.1 3325 + 3326 + y18n@5.0.8: {} 3327 + 3328 + yargs-parser@21.1.1: {} 3329 + 3330 + yargs@17.7.2: 3331 + dependencies: 3332 + cliui: 8.0.1 3333 + escalade: 3.2.0 3334 + get-caller-file: 2.0.5 3335 + require-directory: 2.1.1 3336 + string-width: 4.2.3 3337 + y18n: 5.0.8 3338 + yargs-parser: 21.1.1 3339 + 3340 + yocto-queue@0.1.0: {} 3341 + 3342 + zod@3.25.76: {}
+2
packages/moover/pnpm-workspace.yaml
···
··· 1 + onlyBuiltDependencies: 2 + - core-js
+4 -12
packages/moover/tsconfig.json
··· 1 { 2 - "include": [ 3 - "lib/**/*" 4 - ], 5 - "exclude": [ 6 - "node_modules", 7 - "dist", 8 - "types" 9 - ], 10 "compilerOptions": { 11 "allowJs": true, 12 "checkJs": false, ··· 17 // or use "declarationDir": "types" 18 19 "target": "ES2022", 20 - "lib": [ 21 - "ES2022" 22 - ], 23 // add "DOM" if you use browser APIs 24 "skipLibCheck": true, 25 "module": "ESNext", ··· 27 // or "NodeNext" depending on your setup 28 "rootDir": "lib" 29 } 30 - }
··· 1 { 2 + "include": ["lib/**/*"], 3 + "exclude": ["node_modules", "dist", "types"], 4 "compilerOptions": { 5 "allowJs": true, 6 "checkJs": false, ··· 11 // or use "declarationDir": "types" 12 13 "target": "ES2022", 14 + "lib": ["ES2022"], 15 // add "DOM" if you use browser APIs 16 "skipLibCheck": true, 17 "module": "ESNext", ··· 19 // or "NodeNext" depending on your setup 20 "rootDir": "lib" 21 } 22 + }
+10 -10
packages/moover/types/atprotoUtils.d.ts
··· 1 - export const handleResolver: CompositeHandleResolver; 2 - export const docResolver: CompositeDidDocumentResolver<"plc" | "web">; 3 /** 4 * Cleans the handle of @ and some other unicode characters that used to show up when copied from the profile 5 * @param handle {string} 6 * @returns {string} 7 */ 8 - export function cleanHandle(handle: string): string; 9 /** 10 * Convince helper to resolve a handle to a did and then find the PDS url from the did document. 11 * ··· 13 * @returns {Promise<{usersDid: string, pds: string}>} 14 */ 15 export function handleAndPDSResolver(handle: any): Promise<{ 16 - usersDid: string; 17 - pds: string; 18 - }>; 19 /** 20 * Fetches the DID Web from the .well-known/did.json endpoint of the server. 21 * Legacy and was helpful if the web ui and server are on the same domain, not as useful now 22 * @param baseUrl 23 * @returns {Promise<*>} 24 */ 25 - export function fetchPDSMooverDIDWeb(baseUrl: any): Promise<any>; 26 - import { CompositeHandleResolver } from '@atcute/identity-resolver'; 27 - import { CompositeDidDocumentResolver } from '@atcute/identity-resolver'; 28 - //# sourceMappingURL=atprotoUtils.d.ts.map
··· 1 + export const handleResolver: CompositeHandleResolver 2 + export const docResolver: CompositeDidDocumentResolver<'plc' | 'web'> 3 /** 4 * Cleans the handle of @ and some other unicode characters that used to show up when copied from the profile 5 * @param handle {string} 6 * @returns {string} 7 */ 8 + export function cleanHandle(handle: string): string 9 /** 10 * Convince helper to resolve a handle to a did and then find the PDS url from the did document. 11 * ··· 13 * @returns {Promise<{usersDid: string, pds: string}>} 14 */ 15 export function handleAndPDSResolver(handle: any): Promise<{ 16 + usersDid: string 17 + pds: string 18 + }> 19 /** 20 * Fetches the DID Web from the .well-known/did.json endpoint of the server. 21 * Legacy and was helpful if the web ui and server are on the same domain, not as useful now 22 * @param baseUrl 23 * @returns {Promise<*>} 24 */ 25 + export function fetchPDSMooverDIDWeb(baseUrl: any): Promise<any> 26 + import { CompositeHandleResolver } from '@atcute/identity-resolver' 27 + import { CompositeDidDocumentResolver } from '@atcute/identity-resolver' 28 + //# sourceMappingURL=atprotoUtils.d.ts.map
+89 -82
packages/moover/types/backup.d.ts
··· 1 /** 2 * JSDoc type-only import to avoid runtime import errors in the browser. 3 */ 4 - export type InferXRPCBodyOutput = any; 5 /** 6 * JSDoc type-only import to avoid runtime import errors in the browser. 7 * @typedef {import('@atcute/lexicons').InferXRPCBodyOutput} InferXRPCBodyOutput ··· 10 * Logic to sign up and manage backups for pdsmoover.com (or your own selfhosted instance) 11 */ 12 export class BackupService { 13 - /** 14 - * 15 - * @param backupDidWeb {string} - The did:web for the xrpc service for backups, defaults to did:web:pdsmoover.com 16 - */ 17 - constructor(backupDidWeb?: string); 18 - /** 19 - * 20 - * @type {Client} 21 - */ 22 - atCuteClient: Client; 23 - /** 24 - * 25 - * @type {CredentialManager} 26 - */ 27 - atCuteCredentialManager: CredentialManager; 28 - /** 29 - * The did:web for the xrpc service for backups, defaults to pdsmoover.com 30 - * @type {string} 31 - */ 32 - backupDidWeb: string; 33 - /** 34 - * Logs in and returns the backup status. 35 - * To use the rest of the BackupService, it is assumed that this has ran first, 36 - * and the user has successfully signed up. A successful login is a returned null if the user has not signed up. 37 - * or the backup status if they are 38 - * 39 - * If the server requires 2FA, 40 - * it will throw with error.error === 'AuthFactorTokenRequired'. 41 - * @param identifier {string} handle or did 42 - * @param password {string} 43 - * @param {function|null} onStatus - a function that takes a string used to update the UI. 44 - * Like (status) => console.log(status) 45 - * @param twoFactorCode {string|null} 46 - * 47 - * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>|null>} 48 - */ 49 - loginAndStatus(identifier: string, password: string, onStatus?: Function | null, twoFactorCode?: string | null): Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema["output"]> | null>; 50 - /** 51 - * Signs the user up for backups with the service 52 - * @param onStatus 53 - * @returns {Promise<void>} 54 - */ 55 - signUp(onStatus?: any): Promise<void>; 56 - /** 57 - * Requests a PLC token to be sent to the user's email, needed to add a new rotation key 58 - * @returns {Promise<void>} 59 - */ 60 - requestAPlcToken(): Promise<void>; 61 - /** 62 - * Adds a new rotation to the users did document. Assumes you are already signed in. 63 - * 64 - * WARNING: This will overwrite any existing rotation keys with the new one at the top, and the PDS key as the second one 65 - * @param plcToken {string} - PLC token from the user's email that was sent from requestAPlcToken 66 - * @param rotationKey {string} - The new rotation key to add to the user's did document 67 - * @returns {Promise<void>} 68 - */ 69 - addANewRotationKey(plcToken: string, rotationKey: string): Promise<void>; 70 - /** 71 - * 72 - * Gets the current status of the user's backup repository. 73 - * 74 - * @param onStatus {function|null} - a function that takes a string used to update the UI. 75 - * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>>} 76 - */ 77 - getUsersRepoStatus(onStatus?: Function | null): Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema["output"]>>; 78 - /** 79 - * Requests a backup to be run immediately for the signed-in user. Usually does, depend on the server's backup queue 80 - * @param onStatus 81 - * @returns {Promise<boolean>} 82 - */ 83 - runBackupNow(onStatus?: any): Promise<boolean>; 84 - /** 85 - * Remove (delete) the signed-in user's backup repository. this also deletes all the user's backup data. 86 - * @param onStatus 87 - * @returns {Promise<boolean>} 88 - */ 89 - removeRepo(onStatus?: any): Promise<boolean>; 90 } 91 - import { Client } from '@atcute/client'; 92 - import { CredentialManager } from '@atcute/client'; 93 - import { ComPdsmooverBackupDescribeServer } from '@pds-moover/lexicons'; 94 - //# sourceMappingURL=backup.d.ts.map
··· 1 /** 2 * JSDoc type-only import to avoid runtime import errors in the browser. 3 */ 4 + export type InferXRPCBodyOutput = any 5 /** 6 * JSDoc type-only import to avoid runtime import errors in the browser. 7 * @typedef {import('@atcute/lexicons').InferXRPCBodyOutput} InferXRPCBodyOutput ··· 10 * Logic to sign up and manage backups for pdsmoover.com (or your own selfhosted instance) 11 */ 12 export class BackupService { 13 + /** 14 + * 15 + * @param backupDidWeb {string} - The did:web for the xrpc service for backups, defaults to did:web:pdsmoover.com 16 + */ 17 + constructor(backupDidWeb?: string) 18 + /** 19 + * 20 + * @type {Client} 21 + */ 22 + atCuteClient: Client 23 + /** 24 + * 25 + * @type {CredentialManager} 26 + */ 27 + atCuteCredentialManager: CredentialManager 28 + /** 29 + * The did:web for the xrpc service for backups, defaults to pdsmoover.com 30 + * @type {string} 31 + */ 32 + backupDidWeb: string 33 + /** 34 + * Logs in and returns the backup status. 35 + * To use the rest of the BackupService, it is assumed that this has ran first, 36 + * and the user has successfully signed up. A successful login is a returned null if the user has not signed up. 37 + * or the backup status if they are 38 + * 39 + * If the server requires 2FA, 40 + * it will throw with error.error === 'AuthFactorTokenRequired'. 41 + * @param identifier {string} handle or did 42 + * @param password {string} 43 + * @param {function|null} onStatus - a function that takes a string used to update the UI. 44 + * Like (status) => console.log(status) 45 + * @param twoFactorCode {string|null} 46 + * 47 + * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>|null>} 48 + */ 49 + loginAndStatus( 50 + identifier: string, 51 + password: string, 52 + onStatus?: Function | null, 53 + twoFactorCode?: string | null, 54 + ): Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']> | null> 55 + /** 56 + * Signs the user up for backups with the service 57 + * @param onStatus 58 + * @returns {Promise<void>} 59 + */ 60 + signUp(onStatus?: any): Promise<void> 61 + /** 62 + * Requests a PLC token to be sent to the user's email, needed to add a new rotation key 63 + * @returns {Promise<void>} 64 + */ 65 + requestAPlcToken(): Promise<void> 66 + /** 67 + * Adds a new rotation to the users did document. Assumes you are already signed in. 68 + * 69 + * WARNING: This will overwrite any existing rotation keys with the new one at the top, and the PDS key as the second one 70 + * @param plcToken {string} - PLC token from the user's email that was sent from requestAPlcToken 71 + * @param rotationKey {string} - The new rotation key to add to the user's did document 72 + * @returns {Promise<void>} 73 + */ 74 + addANewRotationKey(plcToken: string, rotationKey: string): Promise<void> 75 + /** 76 + * 77 + * Gets the current status of the user's backup repository. 78 + * 79 + * @param onStatus {function|null} - a function that takes a string used to update the UI. 80 + * @returns {Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>>} 81 + */ 82 + getUsersRepoStatus( 83 + onStatus?: Function | null, 84 + ): Promise<InferXRPCBodyOutput<ComPdsmooverBackupDescribeServer.mainSchema['output']>> 85 + /** 86 + * Requests a backup to be run immediately for the signed-in user. Usually does, depend on the server's backup queue 87 + * @param onStatus 88 + * @returns {Promise<boolean>} 89 + */ 90 + runBackupNow(onStatus?: any): Promise<boolean> 91 + /** 92 + * Remove (delete) the signed-in user's backup repository. this also deletes all the user's backup data. 93 + * @param onStatus 94 + * @returns {Promise<boolean>} 95 + */ 96 + removeRepo(onStatus?: any): Promise<boolean> 97 } 98 + import { Client } from '@atcute/client' 99 + import { CredentialManager } from '@atcute/client' 100 + import { ComPdsmooverBackupDescribeServer } from '@pds-moover/lexicons' 101 + //# sourceMappingURL=backup.d.ts.map
+8 -8
packages/moover/types/main.d.ts
··· 1 - import { Migrator } from './pdsmoover.js'; 2 - import { MissingBlobs } from './missingBlobs.js'; 3 - import { BackupService } from './backup.js'; 4 - import { PlcOps } from './plc-ops.js'; 5 - import { Restore } from './restore.js'; 6 - import { handleAndPDSResolver } from './atprotoUtils.js'; 7 - export { Migrator, MissingBlobs, BackupService, PlcOps, Restore, handleAndPDSResolver }; 8 - //# sourceMappingURL=main.d.ts.map
··· 1 + import { Migrator } from './pdsmoover.js' 2 + import { MissingBlobs } from './missingBlobs.js' 3 + import { BackupService } from './backup.js' 4 + import { PlcOps } from './plc-ops.js' 5 + import { Restore } from './restore.js' 6 + import { handleAndPDSResolver } from './atprotoUtils.js' 7 + export { Migrator, MissingBlobs, BackupService, PlcOps, Restore, handleAndPDSResolver } 8 + //# sourceMappingURL=main.d.ts.map
+65 -57
packages/moover/types/missingBlobs.d.ts
··· 2 * Class to help find missing blobs from the did's previous PDS and import them into the current PDS 3 */ 4 export class MissingBlobs { 5 - /** 6 - * The user's current PDS agent 7 - * @type {AtpAgent} 8 - */ 9 - currentPdsAgent: AtpAgent; 10 - /** 11 - * The user's old PDS agent 12 - * @type {AtpAgent} 13 - */ 14 - oldPdsAgent: AtpAgent; 15 - /** 16 - * the user's did 17 - * @type {string|null} 18 - */ 19 - did: string | null; 20 - /** 21 - * The user's current PDS url 22 - * @type {null} 23 - */ 24 - currentPdsUrl: any; 25 - /** 26 - * A list of the missing cids blobs from the old PDS. In this case if a retry upload fails it gets put in this array for the ui 27 - * @type {string[]} 28 - */ 29 - missingBlobs: string[]; 30 - /** 31 - * Logs the user into the current PDS and gets the account status 32 - * @param handle {string} 33 - * @param password {string} 34 - * @param twoFactorCode {string|null} 35 - * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 36 - */ 37 - currentAgentLogin(handle: string, password: string, twoFactorCode?: string | null): Promise<{ 38 - accountStatus: OutputSchema; 39 - missingBlobsCount: number; 40 - }>; 41 - /** 42 - * Logs into the old PDS and gets the account status. 43 - * Does not need a handle 44 - * since it is assumed the user has already logged in with the current PDS and we are using their did 45 - * @param password {string} 46 - * @param twoFactorCode {string|null} 47 - * @param pdsUrl {string|null} - If you know the url of the old PDS you can pass it in here. If not it will be guessed at from plc ops 48 - * @returns {Promise<void>} 49 - */ 50 - oldAgentLogin(password: string, twoFactorCode?: string | null, pdsUrl?: string | null): Promise<void>; 51 - /** 52 - * Gets the missing blobs from the old PDS and uploads them to the current PDS 53 - * @param statusUpdateHandler {function} - A function to update the status of the migration. This is useful for showing the user the progress of the migration 54 - * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 55 - */ 56 - migrateMissingBlobs(statusUpdateHandler: Function): Promise<{ 57 - accountStatus: OutputSchema; 58 - missingBlobsCount: number; 59 - }>; 60 } 61 - import { AtpAgent } from '@atproto/api'; 62 - //# sourceMappingURL=missingBlobs.d.ts.map
··· 2 * Class to help find missing blobs from the did's previous PDS and import them into the current PDS 3 */ 4 export class MissingBlobs { 5 + /** 6 + * The user's current PDS agent 7 + * @type {AtpAgent} 8 + */ 9 + currentPdsAgent: AtpAgent 10 + /** 11 + * The user's old PDS agent 12 + * @type {AtpAgent} 13 + */ 14 + oldPdsAgent: AtpAgent 15 + /** 16 + * the user's did 17 + * @type {string|null} 18 + */ 19 + did: string | null 20 + /** 21 + * The user's current PDS url 22 + * @type {null} 23 + */ 24 + currentPdsUrl: any 25 + /** 26 + * A list of the missing cids blobs from the old PDS. In this case if a retry upload fails it gets put in this array for the ui 27 + * @type {string[]} 28 + */ 29 + missingBlobs: string[] 30 + /** 31 + * Logs the user into the current PDS and gets the account status 32 + * @param handle {string} 33 + * @param password {string} 34 + * @param twoFactorCode {string|null} 35 + * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 36 + */ 37 + currentAgentLogin( 38 + handle: string, 39 + password: string, 40 + twoFactorCode?: string | null, 41 + ): Promise<{ 42 + accountStatus: OutputSchema 43 + missingBlobsCount: number 44 + }> 45 + /** 46 + * Logs into the old PDS and gets the account status. 47 + * Does not need a handle 48 + * since it is assumed the user has already logged in with the current PDS and we are using their did 49 + * @param password {string} 50 + * @param twoFactorCode {string|null} 51 + * @param pdsUrl {string|null} - If you know the url of the old PDS you can pass it in here. If not it will be guessed at from plc ops 52 + * @returns {Promise<void>} 53 + */ 54 + oldAgentLogin( 55 + password: string, 56 + twoFactorCode?: string | null, 57 + pdsUrl?: string | null, 58 + ): Promise<void> 59 + /** 60 + * Gets the missing blobs from the old PDS and uploads them to the current PDS 61 + * @param statusUpdateHandler {function} - A function to update the status of the migration. This is useful for showing the user the progress of the migration 62 + * @returns {Promise<{accountStatus: OutputSchema, missingBlobsCount: number}>} 63 + */ 64 + migrateMissingBlobs(statusUpdateHandler: Function): Promise<{ 65 + accountStatus: OutputSchema 66 + missingBlobsCount: number 67 + }> 68 } 69 + import { AtpAgent } from '@atproto/api' 70 + //# sourceMappingURL=missingBlobs.d.ts.map
+79 -64
packages/moover/types/pdsmoover.d.ts
··· 3 * On pdsmoover.com this is the logic for the MOOver 4 */ 5 export class Migrator { 6 - /** @type {AtpAgent} */ 7 - oldAgent: AtpAgent; 8 - /** @type {AtpAgent} */ 9 - newAgent: AtpAgent; 10 - /** @type {[string]} */ 11 - missingBlobs: [string]; 12 - /** @type {boolean} */ 13 - createNewAccount: boolean; 14 - /** @type {boolean} */ 15 - migrateRepo: boolean; 16 - /** @type {boolean} */ 17 - migrateBlobs: boolean; 18 - /** @type {boolean} */ 19 - migrateMissingBlobs: boolean; 20 - /** @type {boolean} */ 21 - migratePrefs: boolean; 22 - /** @type {boolean} */ 23 - migratePlcRecord: boolean; 24 - /** 25 - * This migrator is pretty cut and dry and makes a few assumptions 26 - * 1. You are using the same password between each account 27 - * 2. If this command fails for something like oauth 2fa code it throws an error and expects the same values when ran again. 28 - * 3. You can control which "actions" happen by setting the class variables to false. 29 - * 4. Each instance of the class is assumed to be for a single migration 30 - * @param {string} oldHandle - The handle you use on your old pds, something like alice.bsky.social 31 - * @param {string} password - Your password for your current login. Has to be your real password, no app password. When setting up a new account we reuse it as well for that account 32 - * @param {string} newPdsUrl - The new URL for your pds. Like https://coolnewpds.com 33 - * @param {string} newEmail - The email you want to use on the new pds (can be the same as the previous one as long as it's not already being used on the new pds) 34 - * @param {string} newHandle - The new handle you want, like alice.bsky.social, or if you already have a domain name set as a handle can use it myname.com. 35 - * @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one 36 - * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status) 37 - * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 38 - * @param verificationCode - Optional verification captcha code for account creation if the PDS requires it 39 - */ 40 - migrate(oldHandle: string, password: string, newPdsUrl: string, newEmail: string, newHandle: string, inviteCode: string | null, statusUpdateHandler?: Function | null, twoFactorCode?: string | null, verificationCode?: any): Promise<void>; 41 - /** 42 - * Sign and submits the PLC operation to officially migrate the account 43 - * @param {string} token - the PLC token sent in the email. If you're just wanting to run this rerun migrate with all the flags set as false except for migratePlcRecord 44 - * @param additionalRotationKeysToAdd {string[]} - additional rotation keys to add in addition to the ones provided by the new PDS. 45 - * @returns {Promise<void>} 46 - */ 47 - signPlcOperation(token: string, additionalRotationKeysToAdd?: string[]): Promise<void>; 48 - /** 49 - * Using this method assumes the Migrator class was constructed new and this was called. 50 - * Find the user's previous PDS from the PLC op logs, 51 - * logs in and deactivates their old account if it was found still active. 52 - * 53 - * @param oldHandle {string} 54 - * @param oldPassword {string} 55 - * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. 56 - * Like (status) => console.log(status) 57 - * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 58 - * @returns {Promise<void>} 59 - */ 60 - deactivateOldAccount(oldHandle: string, oldPassword: string, statusUpdateHandler?: Function | null, twoFactorCode?: string | null): Promise<void>; 61 - /** 62 - * Signs the logged-in user in this.newAgent for backups with PDS MOOver. This is usually called after migrate and signPlcOperation are successful 63 - * 64 - * @param {string} didWeb 65 - * @returns {Promise<void>} 66 - */ 67 - signUpForBackupsFromMigration(didWeb?: string): Promise<void>; 68 } 69 - import { AtpAgent } from '@atproto/api'; 70 - //# sourceMappingURL=pdsmoover.d.ts.map
··· 3 * On pdsmoover.com this is the logic for the MOOver 4 */ 5 export class Migrator { 6 + /** @type {AtpAgent} */ 7 + oldAgent: AtpAgent 8 + /** @type {AtpAgent} */ 9 + newAgent: AtpAgent 10 + /** @type {[string]} */ 11 + missingBlobs: [string] 12 + /** @type {boolean} */ 13 + createNewAccount: boolean 14 + /** @type {boolean} */ 15 + migrateRepo: boolean 16 + /** @type {boolean} */ 17 + migrateBlobs: boolean 18 + /** @type {boolean} */ 19 + migrateMissingBlobs: boolean 20 + /** @type {boolean} */ 21 + migratePrefs: boolean 22 + /** @type {boolean} */ 23 + migratePlcRecord: boolean 24 + /** 25 + * This migrator is pretty cut and dry and makes a few assumptions 26 + * 1. You are using the same password between each account 27 + * 2. If this command fails for something like oauth 2fa code it throws an error and expects the same values when ran again. 28 + * 3. You can control which "actions" happen by setting the class variables to false. 29 + * 4. Each instance of the class is assumed to be for a single migration 30 + * @param {string} oldHandle - The handle you use on your old pds, something like alice.bsky.social 31 + * @param {string} password - Your password for your current login. Has to be your real password, no app password. When setting up a new account we reuse it as well for that account 32 + * @param {string} newPdsUrl - The new URL for your pds. Like https://coolnewpds.com 33 + * @param {string} newEmail - The email you want to use on the new pds (can be the same as the previous one as long as it's not already being used on the new pds) 34 + * @param {string} newHandle - The new handle you want, like alice.bsky.social, or if you already have a domain name set as a handle can use it myname.com. 35 + * @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one 36 + * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status) 37 + * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 38 + * @param verificationCode - Optional verification captcha code for account creation if the PDS requires it 39 + */ 40 + migrate( 41 + oldHandle: string, 42 + password: string, 43 + newPdsUrl: string, 44 + newEmail: string, 45 + newHandle: string, 46 + inviteCode: string | null, 47 + statusUpdateHandler?: Function | null, 48 + twoFactorCode?: string | null, 49 + verificationCode?: any, 50 + ): Promise<void> 51 + /** 52 + * Sign and submits the PLC operation to officially migrate the account 53 + * @param {string} token - the PLC token sent in the email. If you're just wanting to run this rerun migrate with all the flags set as false except for migratePlcRecord 54 + * @param additionalRotationKeysToAdd {string[]} - additional rotation keys to add in addition to the ones provided by the new PDS. 55 + * @returns {Promise<void>} 56 + */ 57 + signPlcOperation(token: string, additionalRotationKeysToAdd?: string[]): Promise<void> 58 + /** 59 + * Using this method assumes the Migrator class was constructed new and this was called. 60 + * Find the user's previous PDS from the PLC op logs, 61 + * logs in and deactivates their old account if it was found still active. 62 + * 63 + * @param oldHandle {string} 64 + * @param oldPassword {string} 65 + * @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. 66 + * Like (status) => console.log(status) 67 + * @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required 68 + * @returns {Promise<void>} 69 + */ 70 + deactivateOldAccount( 71 + oldHandle: string, 72 + oldPassword: string, 73 + statusUpdateHandler?: Function | null, 74 + twoFactorCode?: string | null, 75 + ): Promise<void> 76 + /** 77 + * Signs the logged-in user in this.newAgent for backups with PDS MOOver. This is usually called after migrate and signPlcOperation are successful 78 + * 79 + * @param {string} didWeb 80 + * @returns {Promise<void>} 81 + */ 82 + signUpForBackupsFromMigration(didWeb?: string): Promise<void> 83 } 84 + import { AtpAgent } from '@atproto/api' 85 + //# sourceMappingURL=pdsmoover.d.ts.map
+109 -98
packages/moover/types/plc-ops.d.ts
··· 1 /** 2 * JSDoc type-only import to avoid runtime import errors in the browser. 3 */ 4 - export type defs = typeof defs; 5 /** 6 * JSDoc type-only import to avoid runtime import errors in the browser. 7 */ 8 - export type normalizeOp = any; 9 /** 10 * JSDoc type-only import to avoid runtime import errors in the browser. 11 */ 12 - export type Operation = import("@atcute/did-plc").Operation; 13 /** 14 * JSDoc type-only import to avoid runtime import errors in the browser. 15 */ 16 - export type CompatibleOperation = import("@atcute/did-plc").CompatibleOperation; 17 /** 18 * JSDoc type-only import to avoid runtime import errors in the browser. 19 */ 20 - export type IndexedEntryLog = import("@atcute/did-plc").IndexedEntryLog; 21 /** 22 * JSDoc type-only import to avoid runtime import errors in the browser. 23 */ 24 - export type IndexedEntry = import("@atcute/did-plc").IndexedEntry; 25 /** 26 * Class to help with various PLC operations 27 */ 28 export class PlcOps { 29 - /** 30 - * 31 - * @param plcDirectoryUrl {string} - The url of the plc directory, defaults to https://plc.directory 32 - */ 33 - constructor(plcDirectoryUrl?: string); 34 - /** 35 - * The url of the plc directory 36 - * @type {string} 37 - */ 38 - plcDirectoryUrl: string; 39 - /** 40 - * Gets the current rotation keys for a user via their last PlC operation 41 - * @param did 42 - * @returns {Promise<string[]>} 43 - */ 44 - getCurrentRotationKeysForUser(did: any): Promise<string[]>; 45 - /** 46 - * Gets the last PlC operation for a user from the plc directory 47 - * @param did 48 - * @returns {Promise<{lastOperation: Operation, base: any}>} 49 - */ 50 - getLastPlcOpFromPlc(did: any): Promise<{ 51 - lastOperation: Operation; 52 - base: any; 53 - }>; 54 - /** 55 - * 56 - * @param logs {IndexedEntryLog} 57 - * @returns {{lastOperation: Operation, base: IndexedEntry}} 58 - */ 59 - getLastPlcOp(logs: IndexedEntryLog): { 60 - lastOperation: Operation; 61 - base: IndexedEntry; 62 - }; 63 - /** 64 - * Gets the plc audit logs for a user from the plc directory 65 - * @param did 66 - * @returns {Promise<IndexedEntryLog>} 67 - */ 68 - getPlcAuditLogs(did: any): Promise<IndexedEntryLog>; 69 - /** 70 - * Creates a new secp256k1 key that can be used for either rotation or verification key 71 - * @returns {Promise<{privateKey: string, publicKey: `did:key:${string}`}>} 72 - */ 73 - createANewSecp256k1(): Promise<{ 74 - privateKey: string; 75 - publicKey: `did:key:${string}`; 76 - }>; 77 - /** 78 - * Signs a new operation with the provided signing key, and information and submits it to the plc directory 79 - * @param did {string} - The user's did 80 - * @param signingRotationKey { P256PrivateKey|Secp256k1PrivateKey} - The keypair to sign the op with 81 - * @param alsoKnownAs {string[]} 82 - * @param rotationKeys {string[]} 83 - * @param pds {string} 84 - * @param verificationKey {string} - The public verification key 85 - * @param prev {string} - The previous valid operation's cid. 86 - * @returns {Promise<void>} 87 - */ 88 - signAndPublishNewOp(did: string, signingRotationKey: P256PrivateKey | Secp256k1PrivateKey, alsoKnownAs: string[], rotationKeys: string[], pds: string, verificationKey: string, prev: string): Promise<void>; 89 - /** 90 - * Takes a multi or hex based private key and returns a keypair 91 - * @param privateKeyString {string} 92 - * @param type {string} - secp256k1 or p256, needed if the private key is hex based, can be assumed if it's a multikey 93 - * @returns {Promise<{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}>} 94 - */ 95 - getKeyPair(privateKeyString: string, type?: string): Promise<{ 96 - type: string; 97 - didPublicKey: `did:key:${string}`; 98 - keypair: P256PrivateKey | Secp256k1PrivateKey; 99 - }>; 100 - /** 101 - * Submits a new operation to the plc directory 102 - * @param did {string} - The user's did 103 - * @param operation 104 - * @returns {Promise<void>} 105 - */ 106 - pushPlcOperation(did: string, operation: any): Promise<void>; 107 - /** 108 - * Creates a new service auth token for a user. This is what is used to create a new account on a PDS for your did 109 - * 110 - * @param iss The user's did 111 - * @param aud The did:web, if it's a PDS it's usually from /xrpc/com.atproto.server.describeServer 112 - * @param keypair The keypair to sign with only supporting ES256K atm 113 - * @param lxm The lxm which is usually com.atproto.server.createAccount for creating a new account 114 - * @returns {Promise<string>} 115 - */ 116 - createANewServiceAuthToken(iss: any, aud: any, keypair: any, lxm: any): Promise<string>; 117 } 118 - import { defs } from '@atcute/did-plc'; 119 - import { P256PrivateKey } from '@atcute/crypto'; 120 - import { Secp256k1PrivateKey } from '@atcute/crypto'; 121 - //# sourceMappingURL=plc-ops.d.ts.map
··· 1 /** 2 * JSDoc type-only import to avoid runtime import errors in the browser. 3 */ 4 + export type defs = typeof defs 5 /** 6 * JSDoc type-only import to avoid runtime import errors in the browser. 7 */ 8 + export type normalizeOp = any 9 /** 10 * JSDoc type-only import to avoid runtime import errors in the browser. 11 */ 12 + export type Operation = import('@atcute/did-plc').Operation 13 /** 14 * JSDoc type-only import to avoid runtime import errors in the browser. 15 */ 16 + export type CompatibleOperation = import('@atcute/did-plc').CompatibleOperation 17 /** 18 * JSDoc type-only import to avoid runtime import errors in the browser. 19 */ 20 + export type IndexedEntryLog = import('@atcute/did-plc').IndexedEntryLog 21 /** 22 * JSDoc type-only import to avoid runtime import errors in the browser. 23 */ 24 + export type IndexedEntry = import('@atcute/did-plc').IndexedEntry 25 /** 26 * Class to help with various PLC operations 27 */ 28 export class PlcOps { 29 + /** 30 + * 31 + * @param plcDirectoryUrl {string} - The url of the plc directory, defaults to https://plc.directory 32 + */ 33 + constructor(plcDirectoryUrl?: string) 34 + /** 35 + * The url of the plc directory 36 + * @type {string} 37 + */ 38 + plcDirectoryUrl: string 39 + /** 40 + * Gets the current rotation keys for a user via their last PlC operation 41 + * @param did 42 + * @returns {Promise<string[]>} 43 + */ 44 + getCurrentRotationKeysForUser(did: any): Promise<string[]> 45 + /** 46 + * Gets the last PlC operation for a user from the plc directory 47 + * @param did 48 + * @returns {Promise<{lastOperation: Operation, base: any}>} 49 + */ 50 + getLastPlcOpFromPlc(did: any): Promise<{ 51 + lastOperation: Operation 52 + base: any 53 + }> 54 + /** 55 + * 56 + * @param logs {IndexedEntryLog} 57 + * @returns {{lastOperation: Operation, base: IndexedEntry}} 58 + */ 59 + getLastPlcOp(logs: IndexedEntryLog): { 60 + lastOperation: Operation 61 + base: IndexedEntry 62 + } 63 + /** 64 + * Gets the plc audit logs for a user from the plc directory 65 + * @param did 66 + * @returns {Promise<IndexedEntryLog>} 67 + */ 68 + getPlcAuditLogs(did: any): Promise<IndexedEntryLog> 69 + /** 70 + * Creates a new secp256k1 key that can be used for either rotation or verification key 71 + * @returns {Promise<{privateKey: string, publicKey: `did:key:${string}`}>} 72 + */ 73 + createANewSecp256k1(): Promise<{ 74 + privateKey: string 75 + publicKey: `did:key:${string}` 76 + }> 77 + /** 78 + * Signs a new operation with the provided signing key, and information and submits it to the plc directory 79 + * @param did {string} - The user's did 80 + * @param signingRotationKey { P256PrivateKey|Secp256k1PrivateKey} - The keypair to sign the op with 81 + * @param alsoKnownAs {string[]} 82 + * @param rotationKeys {string[]} 83 + * @param pds {string} 84 + * @param verificationKey {string} - The public verification key 85 + * @param prev {string} - The previous valid operation's cid. 86 + * @returns {Promise<void>} 87 + */ 88 + signAndPublishNewOp( 89 + did: string, 90 + signingRotationKey: P256PrivateKey | Secp256k1PrivateKey, 91 + alsoKnownAs: string[], 92 + rotationKeys: string[], 93 + pds: string, 94 + verificationKey: string, 95 + prev: string, 96 + ): Promise<void> 97 + /** 98 + * Takes a multi or hex based private key and returns a keypair 99 + * @param privateKeyString {string} 100 + * @param type {string} - secp256k1 or p256, needed if the private key is hex based, can be assumed if it's a multikey 101 + * @returns {Promise<{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}>} 102 + */ 103 + getKeyPair( 104 + privateKeyString: string, 105 + type?: string, 106 + ): Promise<{ 107 + type: string 108 + didPublicKey: `did:key:${string}` 109 + keypair: P256PrivateKey | Secp256k1PrivateKey 110 + }> 111 + /** 112 + * Submits a new operation to the plc directory 113 + * @param did {string} - The user's did 114 + * @param operation 115 + * @returns {Promise<void>} 116 + */ 117 + pushPlcOperation(did: string, operation: any): Promise<void> 118 + /** 119 + * Creates a new service auth token for a user. This is what is used to create a new account on a PDS for your did 120 + * 121 + * @param iss The user's did 122 + * @param aud The did:web, if it's a PDS it's usually from /xrpc/com.atproto.server.describeServer 123 + * @param keypair The keypair to sign with only supporting ES256K atm 124 + * @param lxm The lxm which is usually com.atproto.server.createAccount for creating a new account 125 + * @returns {Promise<string>} 126 + */ 127 + createANewServiceAuthToken(iss: any, aud: any, keypair: any, lxm: any): Promise<string> 128 } 129 + import { defs } from '@atcute/did-plc' 130 + import { P256PrivateKey } from '@atcute/crypto' 131 + import { Secp256k1PrivateKey } from '@atcute/crypto' 132 + //# sourceMappingURL=plc-ops.d.ts.map
+92 -77
packages/moover/types/restore.d.ts
··· 1 - export type Operation = import("@atcute/did-plc").Operation; 2 export class Restore { 3 - /** 4 - * 5 - * @param pdsMooverInstance {string} - The url of the pds moover instance to restore from. Defaults to https://pdsmover.com 6 - */ 7 - constructor(pdsMooverInstance?: string); 8 - /** 9 - * If you want to use a different plc directory create your own instance of the plc ops class and pass it in here 10 - * @type {PlcOps} */ 11 - plcOps: PlcOps; 12 - /** 13 - * This is the base url for the pds moover instance used to restore the files from a backup. 14 - * @type {string} 15 - */ 16 - pdsMooverInstance: string; 17 - /** 18 - * To keep it simple, only uses secp256k for the temp verification key that is used to create the new account on the new PDS 19 - * and is temporarily assigned to the user's account on PLC 20 - * @type {null|Secp256k1PrivateKeyExportable} 21 - */ 22 - tempVerificationKeypair: null | Secp256k1PrivateKeyExportable; 23 - /** @type {AtpAgent} */ 24 - atpAgent: AtpAgent; 25 - /** 26 - * The keypair that is used to sign the plc operation 27 - * @type {null|{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}} 28 - */ 29 - recoveryRotationKeyPair: null | { 30 - type: string; 31 - didPublicKey: `did:key:${string}`; 32 - keypair: P256PrivateKey | Secp256k1PrivateKey; 33 - }; 34 - /** 35 - * If this is true we are just restoring the repo and blobs. Ideally for rerunning a restore process after account recovery 36 - * @type {boolean} 37 - */ 38 - RestoreFromBackup: boolean; 39 - /** 40 - * If set to true then it will do the account recovery. Writes a temp key to the did doc, 41 - * create a new account on the new pds, and then submit a new plc op for the pds to have control (finishes the migration, can always restore the backup later) 42 - * @type {boolean} 43 - */ 44 - AccountRecovery: boolean; 45 - /** 46 - * Recovers an account with the users rotation key and restores the repo from a PDS MOOver backup 47 - * This method can fail, and the account was still recovered, it's best to check the PLC logs to see where an account stands before reruns 48 - * @param rotationKey {string} - The users private rotation key, can be a multi key or hex key 49 - * @param rotationKeyType {string} - The type of the key, secp256k1 or p256. Required if the key is in hex format, defaults to secp256k1 50 - * @param currentHandleOrDid {string} - The users current handle or did, if they don't have a DNS record it will have to be their did for success 51 - * @param newPDS {string} - The new PDS url, like https://coolnewpds.com 52 - * @param newHandle {string} - Can be the users DNS handle if it is already setup with their did, if not it's bob.mypds.com 53 - * @param newPassword {string} - The new password for the new account 54 - * @param newEmail {string} - The new email for the new account 55 - * @param inviteCode {string|null} - The invite code for the new PDS if it requires one 56 - * @param cidToRestoreTo {string|null} - The cid of the plc op to restore to, used mostly to revert a fraudulent plc op. Want to give it the last valid operations cid 57 - * @param onStatus {function|null} - A function that takes a string used to update the UI. Like (status) => console.log(status) 58 - * @returns {Promise<void>} If there is a failure during restoring the back up (after the status Success! Restoring your repo...) then your account is most likely 59 - * recovered and future runs need to have the RestoreFromBackup flag set to true and AccountRecovery set to false. 60 - */ 61 - recover(rotationKey: string, rotationKeyType: string, currentHandleOrDid: string, newPDS: string, newHandle: string, newPassword: string, newEmail: string, inviteCode: string | null, cidToRestoreTo?: string | null, onStatus?: Function | null): Promise<void>; 62 - /** 63 - * This method signs the plc operation over to the new PDS and activates the account 64 - * Assumes you have already created a new account during the recovery process and logged in 65 - * Uses the recommended did doc from the PDS as a base and adds the users rotation key to the rotation keys array 66 - * 67 - * @param usersDid 68 - * @param additionalRotationKeysToAdd 69 - * @param prevCid 70 - * @returns {Promise<void>} 71 - */ 72 - signRestorePlcOperation(usersDid: any, additionalRotationKeysToAdd: any[], prevCid: any): Promise<void>; 73 } 74 - import { PlcOps } from './plc-ops.js'; 75 - import { Secp256k1PrivateKeyExportable } from '@atcute/crypto'; 76 - import { AtpAgent } from '@atproto/api'; 77 - import { P256PrivateKey } from '@atcute/crypto'; 78 - import { Secp256k1PrivateKey } from '@atcute/crypto'; 79 - //# sourceMappingURL=restore.d.ts.map
··· 1 + export type Operation = import('@atcute/did-plc').Operation 2 export class Restore { 3 + /** 4 + * 5 + * @param pdsMooverInstance {string} - The url of the pds moover instance to restore from. Defaults to https://pdsmover.com 6 + */ 7 + constructor(pdsMooverInstance?: string) 8 + /** 9 + * If you want to use a different plc directory create your own instance of the plc ops class and pass it in here 10 + * @type {PlcOps} */ 11 + plcOps: PlcOps 12 + /** 13 + * This is the base url for the pds moover instance used to restore the files from a backup. 14 + * @type {string} 15 + */ 16 + pdsMooverInstance: string 17 + /** 18 + * To keep it simple, only uses secp256k for the temp verification key that is used to create the new account on the new PDS 19 + * and is temporarily assigned to the user's account on PLC 20 + * @type {null|Secp256k1PrivateKeyExportable} 21 + */ 22 + tempVerificationKeypair: null | Secp256k1PrivateKeyExportable 23 + /** @type {AtpAgent} */ 24 + atpAgent: AtpAgent 25 + /** 26 + * The keypair that is used to sign the plc operation 27 + * @type {null|{type: string, didPublicKey: `did:key:${string}`, keypair: P256PrivateKey|Secp256k1PrivateKey}} 28 + */ 29 + recoveryRotationKeyPair: null | { 30 + type: string 31 + didPublicKey: `did:key:${string}` 32 + keypair: P256PrivateKey | Secp256k1PrivateKey 33 + } 34 + /** 35 + * If this is true we are just restoring the repo and blobs. Ideally for rerunning a restore process after account recovery 36 + * @type {boolean} 37 + */ 38 + RestoreFromBackup: boolean 39 + /** 40 + * If set to true then it will do the account recovery. Writes a temp key to the did doc, 41 + * create a new account on the new pds, and then submit a new plc op for the pds to have control (finishes the migration, can always restore the backup later) 42 + * @type {boolean} 43 + */ 44 + AccountRecovery: boolean 45 + /** 46 + * Recovers an account with the users rotation key and restores the repo from a PDS MOOver backup 47 + * This method can fail, and the account was still recovered, it's best to check the PLC logs to see where an account stands before reruns 48 + * @param rotationKey {string} - The users private rotation key, can be a multi key or hex key 49 + * @param rotationKeyType {string} - The type of the key, secp256k1 or p256. Required if the key is in hex format, defaults to secp256k1 50 + * @param currentHandleOrDid {string} - The users current handle or did, if they don't have a DNS record it will have to be their did for success 51 + * @param newPDS {string} - The new PDS url, like https://coolnewpds.com 52 + * @param newHandle {string} - Can be the users DNS handle if it is already setup with their did, if not it's bob.mypds.com 53 + * @param newPassword {string} - The new password for the new account 54 + * @param newEmail {string} - The new email for the new account 55 + * @param inviteCode {string|null} - The invite code for the new PDS if it requires one 56 + * @param cidToRestoreTo {string|null} - The cid of the plc op to restore to, used mostly to revert a fraudulent plc op. Want to give it the last valid operations cid 57 + * @param onStatus {function|null} - A function that takes a string used to update the UI. Like (status) => console.log(status) 58 + * @returns {Promise<void>} If there is a failure during restoring the back up (after the status Success! Restoring your repo...) then your account is most likely 59 + * recovered and future runs need to have the RestoreFromBackup flag set to true and AccountRecovery set to false. 60 + */ 61 + recover( 62 + rotationKey: string, 63 + rotationKeyType: string, 64 + currentHandleOrDid: string, 65 + newPDS: string, 66 + newHandle: string, 67 + newPassword: string, 68 + newEmail: string, 69 + inviteCode: string | null, 70 + cidToRestoreTo?: string | null, 71 + onStatus?: Function | null, 72 + ): Promise<void> 73 + /** 74 + * This method signs the plc operation over to the new PDS and activates the account 75 + * Assumes you have already created a new account during the recovery process and logged in 76 + * Uses the recommended did doc from the PDS as a base and adds the users rotation key to the rotation keys array 77 + * 78 + * @param usersDid 79 + * @param additionalRotationKeysToAdd 80 + * @param prevCid 81 + * @returns {Promise<void>} 82 + */ 83 + signRestorePlcOperation( 84 + usersDid: any, 85 + additionalRotationKeysToAdd: any[], 86 + prevCid: any, 87 + ): Promise<void> 88 } 89 + import { PlcOps } from './plc-ops.js' 90 + import { Secp256k1PrivateKeyExportable } from '@atcute/crypto' 91 + import { AtpAgent } from '@atproto/api' 92 + import { P256PrivateKey } from '@atcute/crypto' 93 + import { Secp256k1PrivateKey } from '@atcute/crypto' 94 + //# sourceMappingURL=restore.d.ts.map
+23 -23
packages/moover/vite.config.js
··· 1 - import {dirname, resolve} from 'node:path' 2 - import {fileURLToPath} from 'node:url' 3 - import {defineConfig} from 'vite' 4 5 const __dirname = dirname(fileURLToPath(import.meta.url)) 6 7 export default defineConfig({ 8 - build: { 9 - lib: { 10 - entry: resolve(__dirname, 'lib/main.js'), 11 - name: '@pds-moover/moover', 12 - // the proper extensions will be added 13 - fileName: 'pds-moover', 14 - }, 15 - rollupOptions: { 16 - // // make sure to externalize deps that shouldn't be bundled 17 - // // into your library 18 - // external: ['vue'], 19 - // output: { 20 - // // Provide global variables to use in the UMD build 21 - // // for externalized deps 22 - // globals: { 23 - // vue: 'Vue', 24 - // }, 25 - // }, 26 - }, 27 }, 28 - })
··· 1 + import { dirname, resolve } from 'node:path' 2 + import { fileURLToPath } from 'node:url' 3 + import { defineConfig } from 'vite' 4 5 const __dirname = dirname(fileURLToPath(import.meta.url)) 6 7 export default defineConfig({ 8 + build: { 9 + lib: { 10 + entry: resolve(__dirname, 'lib/main.js'), 11 + name: '@pds-moover/moover', 12 + // the proper extensions will be added 13 + fileName: 'pds-moover', 14 + }, 15 + rollupOptions: { 16 + // // make sure to externalize deps that shouldn't be bundled 17 + // // into your library 18 + // external: ['vue'], 19 + // output: { 20 + // // Provide global variables to use in the UMD build 21 + // // for externalized deps 22 + // globals: { 23 + // vue: 'Vue', 24 + // }, 25 + // }, 26 }, 27 + }, 28 + })
-1
shared/src/jobs/mod.rs
··· 9 use crate::db::models; 10 use crate::db::models::BlobModel; 11 use apalis::prelude::*; 12 - use log::info; 13 use serde::{Deserialize, Serialize}; 14 use serde_json::{self}; 15 use sqlx::{Pool, Postgres, query};
··· 9 use crate::db::models; 10 use crate::db::models::BlobModel; 11 use apalis::prelude::*; 12 use serde::{Deserialize, Serialize}; 13 use serde_json::{self}; 14 use sqlx::{Pool, Postgres, query};