From f07ec976c96063e0015d6dec891a0f1421bc2963 Mon Sep 17 00:00:00 2001 From: Anselm Date: Mon, 7 Aug 2023 16:59:49 +0100 Subject: [PATCH 1/4] Improve tests and ensure non-private sync through server works --- .eslintrc.cjs | 18 + bun.lockb | Bin 3145 -> 0 bytes package.json | 47 +- src/coValue.test.ts | 1 - src/coValue.ts | 37 +- src/contentType.test.ts | 7 +- src/contentType.ts | 4 +- src/crypto.test.ts | 5 +- src/crypto.ts | 6 +- src/node.ts | 331 +---- src/permissions.test.ts | 1 - src/permissions.ts | 8 +- src/sync.test.ts | 611 ++++++--- src/sync.ts | 422 +++++- tsconfig.json | 10 +- yarn.lock | 2781 +++++++++++++++++++++++++++++++++++++++ 16 files changed, 3687 insertions(+), 602 deletions(-) create mode 100644 .eslintrc.cjs delete mode 100755 bun.lockb create mode 100644 yarn.lock diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..132650d83 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + parserOptions: { + project: './tsconfig.json', + }, + root: true, + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }], + "@typescript-eslint/no-floating-promises": "error", + }, + + }; \ No newline at end of file diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index e498e2f15b9c847f2b245b68f0fa209587c189da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3145 zcmd^Bdr(wm6u-a%ipirjA0VB&4t$$LSyJftf~N*g3oRvz0;IoThQ6Gjr#B=brEP z`_6a1*NF^g%6N_`GME|GTrMjrGyB1(Fj+LkT7!vIXf0fcT2PcL{X8j(I@0djKdGuF zkN#z-D}Phx!c{?=mD7#$7R=ALJ=VFcXUgFT&9zExUGm<9BDQbdo~j>pXX_rl8sPzWlr=d6p4cXK+q-fv(nf##u1*7U z%bV)IHO=DsAG3f`2;UNj18rA7m}3gTAAw#^0UojB8xtrx3I00RkShn?OXT<`cp3&J z_y>vK0H72YAIC&I#^8>S_-*3&i1C3B*4k~s%|kZOXd2@gx$}_x!EnO?!$|lqiwEW8 zb)}YUolRvJ`G;f+qhl!tGrz|W71oJF~+dK zp~kjX12R`P?p|hBZn+-P9|kcJUXo`FTcVThXpy&QJWp?|%v|MzO(Q2AWQF^wy5VNdclUQEEitab(REuvVT*X z^2>{9&*)Apn44&q9=D!5EKRE&u77WLiI4Y^bG_QL(ktfhgAJCWD+X%6+K~NiMov;t z=7sRq{G2b|pXtIY!9Yckoc~TQ$tn!|W^TRycaLq&lGs@%y-pmQM0tm0$ z+SmL_T(*5+dD`ofjcwV|g#5tj$nom=_Mqy7Lw_I*$%8i`1ubD(7fLKws%;Zb&i?xR zM8$r_Uv_muZSUiK#&yqiue@|4{E^^ep%jyyH0#(IkIiB6XNq@61h-1N{A*fky92K6 zbm7H4OGNT>awQ$wQ*py`J7e+8IWwZ`g$YaSRcvQide8BxA;B4!H`<~i%qfR$JGSQj zSu^#w=T9|UZfNN%+|&Q&%;?;lC?pzj|KNQ@C3QQ_men^P%L@{;G=^6FC;!OA+|;gn z*iFd)vD<#}u4)stUoy{WXoHy-)Fu;6UR?SGj%8D2F-jS4sAMf#xonP1Z8GT07A?Mg zD8sj%f^FP~{UYHw@zV24E{lyx1>7hMxK?;K;5o)Khvx%1;zjIYH#nd>g%KbQ@yLUG z$cy~gt|NT#SfltQ=f}joTow;Ai@Ot->h77r{H#q;R{89;Y*C=c(kE#ptpp^X@x>=E z_&86KT3~sBqDW0Z-gr-srAk^w$5K={V6b{1RRDNY-~l#LSCBfv;faNFg|DHb@*tIj zhyhOqc(67hwFY?LB#zDo!y_5BQ=B)^cP}>2mXxy`oxVa~IkVc7D{uz0E>))bJ2mll zQqv(awTQJ9tS)3xb&n(p6{Re%;S8k$mT?a2X|tus#4;K~sh;I{VwG{_EHU(IUJnMZ zDdSkCNX@faH7_WzFjK(h1TZL!(4ei*3qmQM$S@qMGr-RZ+FV*<P$Fdoh^l9 acisnlXW4L`WC;NoN2_8H3VZ9Jl)nJ%=VM?1 diff --git a/package.json b/package.json index 45c1931e1..316b1385d 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,30 @@ { - "name": "cojson", - "module": "src/index.ts", - "type": "module", - "license": "MIT", - "devDependencies": { - "bun-types": "latest" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "dependencies": { - "@noble/ciphers": "^0.1.3", - "@noble/curves": "^1.1.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.1", - "fast-json-stable-stringify": "^2.1.0" - } -} \ No newline at end of file + "name": "cojson", + "module": "src/index.ts", + "type": "module", + "license": "MIT", + "devDependencies": { + "@types/jest": "^29.5.3", + "@typescript-eslint/eslint-plugin": "^6.2.1", + "@typescript-eslint/parser": "^6.2.1", + "eslint": "^8.46.0", + "jest": "^29.6.2", + "ts-jest": "^29.1.1", + "typescript": "5.0.2" + }, + "dependencies": { + "@noble/ciphers": "^0.1.3", + "@noble/curves": "^1.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.1", + "fast-json-stable-stringify": "^2.1.0", + "isomorphic-streams": "https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae" + }, + "scripts": { + "test": "jest" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node" + } +} diff --git a/src/coValue.test.ts b/src/coValue.test.ts index f1af174cf..fc78fba8c 100644 --- a/src/coValue.test.ts +++ b/src/coValue.test.ts @@ -1,4 +1,3 @@ -import { expect, test } from "bun:test"; import { CoValue, Transaction, diff --git a/src/coValue.ts b/src/coValue.ts index 6bb8e06a4..a6edc8753 100644 --- a/src/coValue.ts +++ b/src/coValue.ts @@ -187,8 +187,6 @@ export class CoValue { this.content = undefined; - this.node.syncCoValue(this); - const _ = this.getCurrentContent(); return true; @@ -252,12 +250,18 @@ export class CoValue { expectedNewHash ); - return this.tryAddTransactions( + const success = this.tryAddTransactions( sessionID, [transaction], expectedNewHash, signature ); + + if (success) { + void this.node.sync.syncCoValue(this); + } + + return success; } getCurrentContent(): ContentType { @@ -376,7 +380,8 @@ export class CoValue { // Try to find indirect revelation through previousKeys for (const entry of readKeyHistory) { - if (entry.value?.previousKeys?.[keyID]) { + const encryptedPreviousKey = entry.value?.previousKeys?.[keyID]; + if (entry.value && encryptedPreviousKey) { const sealingKeyID = entry.value.keyID; const sealingKeySecret = this.getReadKey(sealingKeyID); @@ -388,7 +393,7 @@ export class CoValue { { sealed: keyID, sealing: sealingKeyID, - encrypted: entry.value.previousKeys[keyID], + encrypted: encryptedPreviousKey, }, sealingKeySecret ); @@ -424,7 +429,9 @@ export class CoValue { return this.sessions[txID.sessionID]?.transactions[txID.txIndex]; } - newContentSince(knownState: CoValueKnownState | undefined): NewContentMessage | undefined { + newContentSince( + knownState: CoValueKnownState | undefined + ): NewContentMessage | undefined { const newContent: NewContentMessage = { action: "newContent", coValueID: this.id, @@ -459,14 +466,28 @@ export class CoValue { }) .filter((x): x is Exclude => !!x) ), - } + }; - if (!newContent.header && Object.keys(newContent.newContent).length === 0) { + if ( + !newContent.header && + Object.keys(newContent.newContent).length === 0 + ) { return undefined; } return newContent; } + + getDependedOnCoValues(): RawCoValueID[] { + return this.header.ruleset.type === "team" + ? expectTeamContent(this.getCurrentContent()) + .keys() + .filter((k): k is AgentID => k.startsWith("agent_")) + .map((agent) => agentIDAsCoValueID(agent)) + : this.header.ruleset.type === "ownedByTeam" + ? [this.header.ruleset.team] + : []; + } } export type AgentID = `agent_${string}`; diff --git a/src/contentType.test.ts b/src/contentType.test.ts index b0adfabf2..c1f313a89 100644 --- a/src/contentType.test.ts +++ b/src/contentType.test.ts @@ -1,4 +1,3 @@ -import { test, expect } from "bun:test"; import { agentIDfromSessionID, getAgent, @@ -87,13 +86,13 @@ test("Can get map entry values at different points in time", () => { content.edit((editable) => { const beforeA = Date.now(); - Bun.sleepSync(1); + while(Date.now() < beforeA + 10){} editable.set("hello", "A", "trusting"); const beforeB = Date.now(); - Bun.sleepSync(1); + while(Date.now() < beforeB + 10){} editable.set("hello", "B", "trusting"); const beforeC = Date.now(); - Bun.sleepSync(1); + while(Date.now() < beforeC + 10){} editable.set("hello", "C", "trusting"); expect(editable.get("hello")).toEqual("C"); expect(editable.getAtTime("hello", Date.now())).toEqual("C"); diff --git a/src/contentType.ts b/src/contentType.ts index 4331d9975..ac63c6d64 100644 --- a/src/contentType.ts +++ b/src/contentType.ts @@ -82,7 +82,7 @@ export class CoMap< return undefined; } - let lastEntry = ops[ops.length - 1]; + const lastEntry = ops[ops.length - 1]!; if (lastEntry.op === "delete") { return undefined; @@ -116,7 +116,7 @@ export class CoMap< return undefined; } - const lastEntry = ops[ops.length - 1]; + const lastEntry = ops[ops.length - 1]!; return lastEntry.txID; } diff --git a/src/crypto.test.ts b/src/crypto.test.ts index fc4e0b1a2..3226ff6b0 100644 --- a/src/crypto.test.ts +++ b/src/crypto.test.ts @@ -1,4 +1,3 @@ -import { expect, test } from "bun:test"; import { getRecipientID, getSignatoryID, @@ -18,7 +17,7 @@ import { } from "./crypto"; import { base58, base64url } from "@scure/base"; import { x25519 } from "@noble/curves/ed25519"; -import { xsalsa20_poly1305 } from "@noble/ciphers/_slow"; +import { xsalsa20_poly1305 } from "@noble/ciphers/salsa"; import { blake3 } from "@noble/hashes/blake3"; import stableStringify from "fast-json-stable-stringify"; @@ -84,7 +83,7 @@ test("Sealing round-trips, but invalid receiver can't unseal", () => { getRecipientID(sender).substring("recipient_z".length) ); const sealedBytes = base64url.decode( - sealed[getRecipientID(recipient1)].substring("sealed_U".length) + sealed[getRecipientID(recipient1)]!.substring("sealed_U".length) ); const sharedSecret = x25519.getSharedSecret(recipient3priv, senderPub); diff --git a/src/crypto.ts b/src/crypto.ts index e1c7820b9..a7a935049 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -2,7 +2,7 @@ import { ed25519, x25519 } from "@noble/curves/ed25519"; import { xsalsa20_poly1305, xsalsa20 } from "@noble/ciphers/salsa"; import { JsonValue } from "./jsonValue"; import { base58, base64url } from "@scure/base"; -import stableStringify from "fast-json-stable-stringify"; +import { default as stableStringify } from "fast-json-stable-stringify"; import { blake3 } from "@noble/hashes/blake3"; import { randomBytes } from "@noble/ciphers/webcrypto/utils"; import { RawCoValueID, SessionID, TransactionID } from "./coValue"; @@ -91,10 +91,10 @@ export function seal( const sealedSet: SealedSet = {}; for (let i = 0; i < recipientsSorted.length; i++) { - const recipient = recipientsSorted[i]; + const recipient = recipientsSorted[i]!; const sharedSecret = x25519.getSharedSecret( senderPriv, - recipientPubs[i] + recipientPubs[i]! ); const sealedBytes = xsalsa20_poly1305(sharedSecret, nOnce).encrypt( diff --git a/src/node.ts b/src/node.ts index 7acf2db6c..df50bd0f9 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,4 +1,3 @@ -import { CoMap } from "./contentType"; import { newRandomKeySecret, seal } from "./crypto"; import { RawCoValueID, @@ -12,31 +11,17 @@ import { getAgentCoValueHeader, CoValueHeader, agentIDfromSessionID, - agentIDAsCoValueID, } from "./coValue"; import { Team, expectTeamContent } from "./permissions"; -import { - NewContentMessage, - Peer, - PeerID, - PeerState, - SessionNewContent, - SubscribeMessage, - SubscribeResponseMessage, - SyncMessage, - UnsubscribeMessage, - WrongAssumedKnownStateMessage, - combinedKnownStates, - weAreStrictlyAhead, -} from "./sync"; +import { SyncManager } from "./sync"; export class LocalNode { coValues: { [key: RawCoValueID]: CoValueState } = {}; - peers: { [key: PeerID]: PeerState } = {}; agentCredential: AgentCredential; agentID: AgentID; ownSessionID: SessionID; knownAgents: { [key: AgentID]: Agent } = {}; + sync = new SyncManager(this); constructor(agentCredential: AgentCredential, ownSessionID: SessionID) { this.agentCredential = agentCredential; @@ -57,7 +42,7 @@ export class LocalNode { const coValue = new CoValue(header, this); this.coValues[coValue.id] = { state: "loaded", coValue: coValue }; - this.syncCoValue(coValue); + void this.sync.syncCoValue(coValue); return coValue; } @@ -69,20 +54,7 @@ export class LocalNode { this.coValues[id] = entry; - for (const peer of Object.values(this.peers)) { - peer.outgoing - .write({ - action: "subscribe", - knownState: { - coValueID: id, - header: false, - sessions: {}, - }, - }) - .catch((e) => { - console.error("Error writing to peer", e); - }); - } + this.sync.loadFromPeers(id); } if (entry.state === "loaded") { return Promise.resolve(entry.coValue); @@ -145,299 +117,6 @@ export class LocalNode { return new Team(teamContent, this); } - addPeer(peer: Peer) { - const peerState: PeerState = { - id: peer.id, - optimisticKnownStates: {}, - incoming: peer.incoming, - outgoing: peer.outgoing.getWriter(), - role: peer.role, - }; - this.peers[peer.id] = peerState; - - if (peer.role === "server") { - for (const entry of Object.values(this.coValues)) { - if (entry.state === "loading") { - continue; - } - - peerState.outgoing - .write({ - action: "subscribe", - knownState: entry.coValue.knownState(), - }) - .catch((e) => { - // TODO: handle error - console.error("Error writing to peer", e); - }); - - peerState.optimisticKnownStates[entry.coValue.id] = { - coValueID: entry.coValue.id, - header: false, - sessions: {}, - }; - } - } - - const readIncoming = async () => { - for await (const msg of peerState.incoming) { - for (const responseMsg of this.handleSyncMessage( - msg, - peerState - )) { - await peerState.outgoing.write(responseMsg); - } - } - }; - - readIncoming().catch((e) => { - // TODO: handle error - console.error("Error reading from peer", e); - }); - } - - handleSyncMessage(msg: SyncMessage, peer: PeerState): SyncMessage[] { - // TODO: validate - switch (msg.action) { - case "subscribe": - return this.handleSubscribe(msg, peer); - case "subscribeResponse": - return this.handleSubscribeResponse(msg, peer); - case "newContent": - return this.handleNewContent(msg); - case "wrongAssumedKnownState": - return this.handleWrongAssumedKnownState(msg, peer); - case "unsubscribe": - return this.handleUnsubscribe(msg); - default: - throw new Error(`Unknown message type ${(msg as any).action}`); - } - } - - handleSubscribe( - msg: SubscribeMessage, - peer: PeerState, - asDependencyOf?: RawCoValueID - ): SyncMessage[] { - const entry = this.coValues[msg.knownState.coValueID]; - - if (!entry || entry.state === "loading") { - if (!entry) { - this.coValues[msg.knownState.coValueID] = newLoadingState(); - } - - return [ - { - action: "subscribeResponse", - knownState: { - coValueID: msg.knownState.coValueID, - header: false, - sessions: {}, - }, - }, - ]; - } - - peer.optimisticKnownStates[entry.coValue.id] = - entry.coValue.knownState(); - - const newContent = entry.coValue.newContentSince(msg.knownState); - - const dependedOnCoValues = - entry.coValue.header.ruleset.type === "team" - ? expectTeamContent(entry.coValue.getCurrentContent()) - .keys() - .filter((k): k is AgentID => k.startsWith("agent_")) - .map((agent) => agentIDAsCoValueID(agent)) - : entry.coValue.header.ruleset.type === "ownedByTeam" - ? [entry.coValue.header.ruleset.team] - : []; - - return [ - ...dependedOnCoValues.flatMap((coValueID) => - this.handleSubscribe( - { - action: "subscribe", - knownState: { - coValueID, - header: false, - sessions: {}, - }, - }, - peer, - asDependencyOf || msg.knownState.coValueID - ) - ), - { - action: "subscribeResponse", - knownState: entry.coValue.knownState(), - asDependencyOf, - }, - ...(newContent ? [newContent] : []), - ]; - } - - handleSubscribeResponse( - msg: SubscribeResponseMessage, - peer: PeerState - ): SyncMessage[] { - let entry = this.coValues[msg.knownState.coValueID]; - - if (!entry) { - if (msg.asDependencyOf) { - if (this.coValues[msg.asDependencyOf]) { - entry = newLoadingState(); - - this.coValues[msg.knownState.coValueID] = entry; - } - } else { - throw new Error( - "Expected coValue entry to be created, missing subscribe?" - ); - } - } - - if (entry.state === "loading") { - peer.optimisticKnownStates[msg.knownState.coValueID] = - msg.knownState; - return []; - } - - const newContent = entry.coValue.newContentSince(msg.knownState); - peer.optimisticKnownStates[msg.knownState.coValueID] = - combinedKnownStates(msg.knownState, entry.coValue.knownState()); - - return newContent ? [newContent] : []; - } - - handleNewContent(msg: NewContentMessage): SyncMessage[] { - let entry = this.coValues[msg.coValueID]; - - if (!entry) { - throw new Error( - "Expected coValue entry to be created, missing subscribe?" - ); - } - - let resolveAfterDone: ((coValue: CoValue) => void) | undefined; - - if (entry.state === "loading") { - if (!msg.header) { - throw new Error("Expected header to be sent in first message"); - } - - const coValue = new CoValue(msg.header, this); - - resolveAfterDone = entry.resolve; - - entry = { - state: "loaded", - coValue: coValue, - }; - - this.coValues[msg.coValueID] = entry; - } - - const coValue = entry.coValue; - - let invalidStateAssumed = false; - - for (const sessionID of Object.keys(msg.newContent) as SessionID[]) { - const ourKnownTxIdx = - coValue.sessions[sessionID]?.transactions.length; - const theirFirstNewTxIdx = msg.newContent[sessionID].after; - - if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) { - invalidStateAssumed = true; - continue; - } - - const alreadyKnownOffset = ourKnownTxIdx - ? ourKnownTxIdx - theirFirstNewTxIdx - : 0; - - const newTransactions = - msg.newContent[sessionID].newTransactions.slice( - alreadyKnownOffset - ); - - const success = coValue.tryAddTransactions( - sessionID, - newTransactions, - msg.newContent[sessionID].lastHash, - msg.newContent[sessionID].lastSignature - ); - - if (!success) { - console.error("Failed to add transactions", newTransactions); - continue; - } - } - - if (resolveAfterDone) { - resolveAfterDone(coValue); - } - - return invalidStateAssumed - ? [ - { - action: "wrongAssumedKnownState", - knownState: coValue.knownState(), - }, - ] - : []; - } - - handleWrongAssumedKnownState( - msg: WrongAssumedKnownStateMessage, - peer: PeerState - ): SyncMessage[] { - const coValue = this.expectCoValueLoaded(msg.knownState.coValueID); - - peer.optimisticKnownStates[msg.knownState.coValueID] = - combinedKnownStates(msg.knownState, coValue.knownState()); - - const newContent = coValue.newContentSince(msg.knownState); - - return newContent ? [newContent] : []; - } - - handleUnsubscribe(msg: UnsubscribeMessage): SyncMessage[] { - throw new Error("Method not implemented."); - } - - async syncCoValue(coValue: CoValue) { - for (const peer of Object.values(this.peers)) { - const optimisticKnownState = - peer.optimisticKnownStates[coValue.id]; - - if (optimisticKnownState || peer.role === "server") { - const newContent = - coValue.newContentSince(optimisticKnownState); - - peer.optimisticKnownStates[coValue.id] = peer - .optimisticKnownStates[coValue.id] - ? combinedKnownStates( - peer.optimisticKnownStates[coValue.id], - coValue.knownState() - ) - : coValue.knownState(); - - if (!optimisticKnownState && peer.role === "server") { - // auto-subscribe - await peer.outgoing.write({ - action: "subscribe", - knownState: coValue.knownState(), - }); - } - - if (newContent) { - await peer.outgoing.write(newContent); - } - } - } - } - testWithDifferentCredentials( agentCredential: AgentCredential, ownSessionID: SessionID @@ -480,7 +159,7 @@ type CoValueState = } | { state: "loaded"; coValue: CoValue }; -function newLoadingState(): CoValueState { +export function newLoadingState(): CoValueState { let resolve: (coValue: CoValue) => void; const promise = new Promise((r) => { diff --git a/src/permissions.test.ts b/src/permissions.test.ts index 3d26fb40c..270c267b0 100644 --- a/src/permissions.test.ts +++ b/src/permissions.test.ts @@ -1,4 +1,3 @@ -import { test, expect } from "bun:test"; import { getAgent, getAgentID, diff --git a/src/permissions.ts b/src/permissions.ts index 224d568cd..8f0a0d0bc 100644 --- a/src/permissions.ts +++ b/src/permissions.ts @@ -290,7 +290,13 @@ export class Team { this.teamMap.coValue.node.agentCredential.recipientSecret, new Set( currentlyPermittedReaders.map( - (reader) => this.node.knownAgents[reader].recipientID + (reader) => { + const readerAgent = this.node.knownAgents[reader]; + if (!readerAgent) { + throw new Error("Unknown agent " + reader); + } + return readerAgent.recipientID + } ) ), { diff --git a/src/sync.test.ts b/src/sync.test.ts index 2698d1224..4bd41715d 100644 --- a/src/sync.test.ts +++ b/src/sync.test.ts @@ -1,13 +1,20 @@ -import { test, expect } from "bun:test"; import { + AgentID, + agentIDAsCoValueID, getAgent, getAgentID, newRandomAgentCredential, newRandomSessionID, } from "./coValue"; import { LocalNode } from "./node"; -import { Peer, SyncMessage } from "./sync"; +import { Peer, PeerID, SyncMessage } from "./sync"; import { MapOpPayload, expectMap } from "./contentType"; +import { Team } from "./permissions"; +import { + ReadableStream, + WritableStream, + TransformStream, +} from "isomorphic-streams"; test( "Node replies with initial tx and header to empty subscribe", @@ -28,7 +35,7 @@ test( const [inRx, inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -39,27 +46,25 @@ test( await writer.write({ action: "subscribe", - knownState: { - coValueID: map.coValue.id, - header: false, - sessions: {}, - }, + coValueID: map.coValue.id, + header: false, + sessions: {}, }); const reader = outRx.getReader(); - const _adminSubscribeResponseMsg = await reader.read(); - const _adminNewContentMsg = await reader.read(); - const _teamSubscribeResponseMsg = await reader.read(); - const _teamNewContentMsg = await reader.read(); + expect((await reader.read()).value).toMatchObject(admStateEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamStateEx(team)); - const subscribeResponseMsg = await reader.read(); - - expect(subscribeResponseMsg.value).toEqual({ - action: "subscribeResponse", - knownState: map.coValue.knownState(), + const mapTellKnownStateMsg = await reader.read(); + expect(mapTellKnownStateMsg.value).toEqual({ + action: "tellKnownState", + ...map.coValue.knownState(), } satisfies SyncMessage); + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); + const newContentMsg = await reader.read(); expect(newContentMsg.value).toEqual({ @@ -76,8 +81,8 @@ test( newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[0].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[0]!.madeAt, changes: [ { op: "insert", @@ -88,14 +93,13 @@ test( }, ], lastHash: - map.coValue.sessions[node.ownSessionID].lastHash!, + map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); }, - { timeout: 100 } ); test("Node replies with only new tx to subscribe with some known state", async () => { @@ -116,7 +120,7 @@ test("Node replies with only new tx to subscribe with some known state", async ( const [inRx, inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -127,29 +131,27 @@ test("Node replies with only new tx to subscribe with some known state", async ( await writer.write({ action: "subscribe", - knownState: { - coValueID: map.coValue.id, - header: true, - sessions: { - [node.ownSessionID]: 1, - }, + coValueID: map.coValue.id, + header: true, + sessions: { + [node.ownSessionID]: 1, }, }); const reader = outRx.getReader(); - const _adminSubscribeResponseMsg = await reader.read(); - const _adminNewContentMsg = await reader.read(); - const _teamSubscribeResponseMsg = await reader.read(); - const _teamNewContentMsg = await reader.read(); + expect((await reader.read()).value).toMatchObject(admStateEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamStateEx(team)); - const mapSubscribeResponseMsg = await reader.read(); - - expect(mapSubscribeResponseMsg.value).toEqual({ - action: "subscribeResponse", - knownState: map.coValue.knownState(), + const mapTellKnownStateMsg = await reader.read(); + expect(mapTellKnownStateMsg.value).toEqual({ + action: "tellKnownState", + ...map.coValue.knownState(), } satisfies SyncMessage); + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); + const mapNewContentMsg = await reader.read(); expect(mapNewContentMsg.value).toEqual({ @@ -162,8 +164,8 @@ test("Node replies with only new tx to subscribe with some known state", async ( newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[1].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[1]!.madeAt, changes: [ { op: "insert", @@ -173,15 +175,17 @@ test("Node replies with only new tx to subscribe with some known state", async ( ], }, ], - lastHash: map.coValue.sessions[node.ownSessionID].lastHash!, + lastHash: map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); }); -test.skip("TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues", () => {}); +test.todo( + "TODO: node only replies with new tx to subscribe with some known state, even in the depended on coValues", +); test("After subscribing, node sends own known state and new txs to peer", async () => { const admin = newRandomAgentCredential(); @@ -196,7 +200,7 @@ test("After subscribing, node sends own known state and new txs to peer", async const [inRx, inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -207,29 +211,27 @@ test("After subscribing, node sends own known state and new txs to peer", async await writer.write({ action: "subscribe", - knownState: { - coValueID: map.coValue.id, - header: false, - sessions: { - [node.ownSessionID]: 0, - }, + coValueID: map.coValue.id, + header: false, + sessions: { + [node.ownSessionID]: 0, }, }); const reader = outRx.getReader(); - const _adminSubscribeResponseMsg = await reader.read(); - const _adminNewContentMsg = await reader.read(); - const _teamSubscribeResponseMsg = await reader.read(); - const _teamNewContentMsg = await reader.read(); + expect((await reader.read()).value).toMatchObject(admStateEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamStateEx(team)); - const mapSubscribeResponseMsg = await reader.read(); - - expect(mapSubscribeResponseMsg.value).toEqual({ - action: "subscribeResponse", - knownState: map.coValue.knownState(), + const mapTellKnownStateMsg = await reader.read(); + expect(mapTellKnownStateMsg.value).toEqual({ + action: "tellKnownState", + ...map.coValue.knownState(), } satisfies SyncMessage); + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); + const mapNewContentHeaderOnlyMsg = await reader.read(); expect(mapNewContentHeaderOnlyMsg.value).toEqual({ @@ -254,8 +256,8 @@ test("After subscribing, node sends own known state and new txs to peer", async newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[0].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[0]!.madeAt, changes: [ { op: "insert", @@ -265,9 +267,9 @@ test("After subscribing, node sends own known state and new txs to peer", async ], }, ], - lastHash: map.coValue.sessions[node.ownSessionID].lastHash!, + lastHash: map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); @@ -287,8 +289,8 @@ test("After subscribing, node sends own known state and new txs to peer", async newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[1].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[1]!.madeAt, changes: [ { op: "insert", @@ -298,15 +300,15 @@ test("After subscribing, node sends own known state and new txs to peer", async ], }, ], - lastHash: map.coValue.sessions[node.ownSessionID].lastHash!, + lastHash: map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); }); -test("Client replies with known new content to subscribeResponse from server", async () => { +test("Client replies with known new content to tellKnownState from server", async () => { const admin = newRandomAgentCredential(); const adminID = getAgentID(getAgent(admin)); @@ -323,31 +325,43 @@ test("Client replies with known new content to subscribeResponse from server", a const [inRx, inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, role: "peer", }); + const reader = outRx.getReader(); + + // expect((await reader.read()).value).toMatchObject(teamStateEx(team)); + const writer = inTx.getWriter(); await writer.write({ - action: "subscribeResponse", - knownState: { - coValueID: map.coValue.id, - header: false, - sessions: { - [node.ownSessionID]: 0, - }, + action: "tellKnownState", + coValueID: map.coValue.id, + header: false, + sessions: { + [node.ownSessionID]: 0, }, }); - const reader = outRx.getReader(); + expect((await reader.read()).value).toMatchObject(admStateEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamStateEx(team)); - const msg1 = await reader.read(); + const mapTellKnownStateMsg = await reader.read(); + expect(mapTellKnownStateMsg.value).toEqual({ + action: "tellKnownState", + ...map.coValue.knownState(), + } satisfies SyncMessage); - expect(msg1.value).toEqual({ + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); + + const mapNewContentMsg = await reader.read(); + + expect(mapNewContentMsg.value).toEqual({ action: "newContent", coValueID: map.coValue.id, header: map.coValue.header, @@ -357,8 +371,8 @@ test("Client replies with known new content to subscribeResponse from server", a newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[0].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[0]!.madeAt, changes: [ { op: "insert", @@ -368,9 +382,9 @@ test("Client replies with known new content to subscribeResponse from server", a ], }, ], - lastHash: map.coValue.sessions[node.ownSessionID].lastHash!, + lastHash: map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); @@ -389,7 +403,7 @@ test("No matter the optimistic known state, node respects invalid known state me const [inRx, inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -400,23 +414,35 @@ test("No matter the optimistic known state, node respects invalid known state me await writer.write({ action: "subscribe", - knownState: { - coValueID: map.coValue.id, - header: false, - sessions: { - [node.ownSessionID]: 0, - }, + coValueID: map.coValue.id, + header: false, + sessions: { + [node.ownSessionID]: 0, }, }); const reader = outRx.getReader(); - const _adminSubscribeResponseMsg = await reader.read(); - const _adminNewContentMsg = await reader.read(); - const _teamSubscribeResponseMsg = await reader.read(); - const _teamNewContentMsg = await reader.read(); - const _mapSubscribeResponseMsg = await reader.read(); - const _mapNewContentHeaderOnlyMsg = await reader.read(); + expect((await reader.read()).value).toMatchObject(admStateEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamStateEx(team)); + + const mapTellKnownStateMsg = await reader.read(); + expect(mapTellKnownStateMsg.value).toEqual({ + action: "tellKnownState", + ...map.coValue.knownState(), + } satisfies SyncMessage); + + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); + + const mapNewContentHeaderOnlyMsg = await reader.read(); + + expect(mapNewContentHeaderOnlyMsg.value).toEqual({ + action: "newContent", + coValueID: map.coValue.id, + header: map.coValue.header, + newContent: {}, + } satisfies SyncMessage); map.edit((editable) => { editable.set("hello", "world", "trusting"); @@ -431,12 +457,10 @@ test("No matter the optimistic known state, node respects invalid known state me await writer.write({ action: "wrongAssumedKnownState", - knownState: { - coValueID: map.coValue.id, - header: true, - sessions: { - [node.ownSessionID]: 1, - }, + coValueID: map.coValue.id, + header: true, + sessions: { + [node.ownSessionID]: 1, }, } satisfies SyncMessage); @@ -452,8 +476,8 @@ test("No matter the optimistic known state, node respects invalid known state me newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[1].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[1]!.madeAt, changes: [ { op: "insert", @@ -463,9 +487,9 @@ test("No matter the optimistic known state, node respects invalid known state me ], }, ], - lastHash: map.coValue.sessions[node.ownSessionID].lastHash!, + lastHash: map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); @@ -481,10 +505,10 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m const map = team.createMap(); - const [inRx, inTx] = newStreamPair(); + const [inRx, _inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -497,7 +521,7 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m const reader = outRx.getReader(); - await shouldNotResolve(reader.read(), { timeout: 50 }); + await expect(shouldNotResolve(reader.read(), {timeout: 100})).resolves.toBeUndefined(); }); test("If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe", async () => { @@ -510,38 +534,45 @@ test("If we add a server peer, all updates to all coValues are sent to it, even const map = team.createMap(); - const [inRx, inTx] = newStreamPair(); + const [inRx, _inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, role: "server", }); + const reader = outRx.getReader(); + expect((await reader.read()).value).toMatchObject({ + action: "subscribe", + coValueID: agentIDAsCoValueID(adminID), + }); + expect((await reader.read()).value).toMatchObject({ + action: "subscribe", + coValueID: team.teamMap.coValue.id, + }); + + const mapSubscribeMsg = await reader.read(); + + expect(mapSubscribeMsg.value).toEqual({ + action: "subscribe", + coValueID: map.coValue.id, + header: true, + sessions: {}, + } satisfies SyncMessage); + map.edit((editable) => { editable.set("hello", "world", "trusting"); }); - const reader = outRx.getReader(); - const _adminSubscribeMsg = await reader.read(); - const _teamSubscribeMsg = await reader.read(); + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); - const subscribeMsg = await reader.read(); + const mapNewContentMsg = await reader.read(); - expect(subscribeMsg.value).toEqual({ - action: "subscribe", - knownState: { - coValueID: map.coValue.id, - header: true, - sessions: {}, - }, - } satisfies SyncMessage); - - const newContentMsg = await reader.read(); - - expect(newContentMsg.value).toEqual({ + expect(mapNewContentMsg.value).toEqual({ action: "newContent", coValueID: map.coValue.id, header: map.coValue.header, @@ -551,8 +582,8 @@ test("If we add a server peer, all updates to all coValues are sent to it, even newTransactions: [ { privacy: "trusting", - madeAt: map.coValue.sessions[node.ownSessionID] - .transactions[0].madeAt, + madeAt: map.coValue.sessions[node.ownSessionID]! + .transactions[0]!.madeAt, changes: [ { op: "insert", @@ -562,9 +593,9 @@ test("If we add a server peer, all updates to all coValues are sent to it, even ], }, ], - lastHash: map.coValue.sessions[node.ownSessionID].lastHash!, + lastHash: map.coValue.sessions[node.ownSessionID]!.lastHash!, lastSignature: - map.coValue.sessions[node.ownSessionID].lastSignature!, + map.coValue.sessions[node.ownSessionID]!.lastSignature!, }, }, } satisfies SyncMessage); @@ -580,10 +611,10 @@ test("If we add a server peer, newly created coValues are auto-subscribed to", a team.createMap(); - const [inRx, inTx] = newStreamPair(); + const [inRx, _inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -591,21 +622,30 @@ test("If we add a server peer, newly created coValues are auto-subscribed to", a }); const reader = outRx.getReader(); - const _initialMsg1 = await reader.read(); - const _initialMsg2 = await reader.read(); + expect((await reader.read()).value).toMatchObject({ + action: "subscribe", + coValueID: agentIDAsCoValueID(adminID), + }); + expect((await reader.read()).value).toMatchObject({ + action: "subscribe", + coValueID: team.teamMap.coValue.id, + }); const map = team.createMap(); - const msg1 = await reader.read(); + const mapSubscribeMsg = await reader.read(); - expect(msg1.value).toEqual({ + expect(mapSubscribeMsg.value).toEqual({ action: "subscribe", - knownState: map.coValue.knownState(), + ...map.coValue.knownState(), } satisfies SyncMessage); - const msg2 = await reader.read(); + expect((await reader.read()).value).toMatchObject(admContEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamContentEx(team)); - expect(msg2.value).toEqual({ + const mapContentMsg = await reader.read(); + + expect(mapContentMsg.value).toEqual({ action: "newContent", coValueID: map.coValue.id, header: map.coValue.header, @@ -613,7 +653,9 @@ test("If we add a server peer, newly created coValues are auto-subscribed to", a } satisfies SyncMessage); }); -test.skip("TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it", () => {}); +test.todo( + "TODO: when receiving a subscribe response that is behind our optimistic state (due to already sent content), we ignore it", +); test("When we connect a new server peer, we try to sync all existing coValues to it", async () => { const admin = newRandomAgentCredential(); @@ -625,10 +667,10 @@ test("When we connect a new server peer, we try to sync all existing coValues to const map = team.createMap(); - const [inRx, inTx] = newStreamPair(); + const [inRx, _inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -642,14 +684,14 @@ test("When we connect a new server peer, we try to sync all existing coValues to expect(teamSubscribeMessage.value).toEqual({ action: "subscribe", - knownState: team.teamMap.coValue.knownState(), + ...team.teamMap.coValue.knownState(), } satisfies SyncMessage); const secondMessage = await reader.read(); expect(secondMessage.value).toEqual({ action: "subscribe", - knownState: map.coValue.knownState(), + ...map.coValue.knownState(), } satisfies SyncMessage); }); @@ -666,7 +708,7 @@ test("When receiving a subscribe with a known state that is ahead of our own, pe const [inRx, inTx] = newStreamPair(); const [outRx, outTx] = newStreamPair(); - node.addPeer({ + node.sync.addPeer({ id: "test", incoming: inRx, outgoing: outTx, @@ -677,30 +719,27 @@ test("When receiving a subscribe with a known state that is ahead of our own, pe await writer.write({ action: "subscribe", - knownState: { - coValueID: map.coValue.id, - header: true, - sessions: { - [node.ownSessionID]: 1, - }, + coValueID: map.coValue.id, + header: true, + sessions: { + [node.ownSessionID]: 1, }, }); const reader = outRx.getReader(); - const _adminSubscribeResponseMsg = await reader.read(); - const _adminNewContentMsg = await reader.read(); - const _teamSubscribeResponseMsg = await reader.read(); - const _teamNewContentMsg = await reader.read(); - const mapSubscribeResponse = await reader.read(); + expect((await reader.read()).value).toMatchObject(admStateEx(adminID)); + expect((await reader.read()).value).toMatchObject(teamStateEx(team)); + const mapTellKnownState = await reader.read(); - expect(mapSubscribeResponse.value).toEqual({ - action: "subscribeResponse", - knownState: map.coValue.knownState(), + expect(mapTellKnownState.value).toEqual({ + action: "tellKnownState", + ...map.coValue.knownState(), } satisfies SyncMessage); }); -test("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => { +test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => { + // TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work const admin = newRandomAgentCredential(); const adminID = getAgentID(getAgent(admin)); @@ -711,63 +750,106 @@ test("When replaying creation and transactions of a coValue as new content, the const [inRx1, inTx1] = newStreamPair(); const [outRx1, outTx1] = newStreamPair(); - node1.addPeer({ + node1.sync.addPeer({ id: "test2", incoming: inRx1, outgoing: outTx1, role: "server", }); - const reader1 = outRx1.getReader(); - - const _adminSubscriptionMsg = await reader1.read(); - const teamSubscribeMsg = await reader1.read(); - - const map = team.createMap(); - - const mapSubscriptionMsg = await reader1.read(); - const mapNewContentMsg = await reader1.read(); - - map.edit((editable) => { - editable.set("hello", "world", "trusting"); - }); - - const mapEditMsg = await reader1.read(); + const to1 = inTx1.getWriter(); + const from1 = outRx1.getReader(); const node2 = new LocalNode(admin, newRandomSessionID(adminID)); const [inRx2, inTx2] = newStreamPair(); const [outRx2, outTx2] = newStreamPair(); - node2.addPeer({ + node2.sync.addPeer({ id: "test1", incoming: inRx2, outgoing: outTx2, role: "client", }); - const writer2 = inTx2.getWriter(); - const reader2 = outRx2.getReader(); + const to2 = inTx2.getWriter(); + const from2 = outRx2.getReader(); - await writer2.write(teamSubscribeMsg.value); - const teamSubscribeResponseMsg = await reader2.read(); + const adminSubscribeMessage = await from1.read(); + expect(adminSubscribeMessage.value).toMatchObject({ + action: "subscribe", + coValueID: agentIDAsCoValueID(adminID), + }); + const teamSubscribeMsg = await from1.read(); + expect(teamSubscribeMsg.value).toMatchObject({ + action: "subscribe", + coValueID: team.teamMap.coValue.id, + }); - expect(node2.coValues[team.teamMap.coValue.id]?.state).toEqual("loading"); + await to2.write(adminSubscribeMessage.value!); + await to2.write(teamSubscribeMsg.value!); - const writer1 = inTx1.getWriter(); + const adminTellKnownStateMsg = await from2.read(); + expect(adminTellKnownStateMsg.value).toMatchObject(admStateEx(adminID)); - await writer1.write(teamSubscribeResponseMsg.value); - const teamContentMsg = await reader1.read(); + const teamTellKnownStateMsg = await from2.read(); + expect(teamTellKnownStateMsg.value).toMatchObject(teamStateEx(team)); - await writer2.write(teamContentMsg.value); + expect( + node2.sync.peers["test1"]!.optimisticKnownStates[ + team.teamMap.coValue.id + ] + ).toBeDefined(); - await writer2.write(mapSubscriptionMsg.value); - const _mapSubscribeResponseMsg = await reader2.read(); - await writer2.write(mapNewContentMsg.value); + await to1.write(adminTellKnownStateMsg.value!); + await to1.write(teamTellKnownStateMsg.value!); + + const adminContentMsg = await from1.read(); + expect(adminContentMsg.value).toMatchObject(admContEx(adminID)); + + const teamContentMsg = await from1.read(); + expect(teamContentMsg.value).toMatchObject(teamContentEx(team)); + + await to2.write(adminContentMsg.value!); + await to2.write(teamContentMsg.value!); + + const map = team.createMap(); + + const mapSubscriptionMsg = await from1.read(); + expect(mapSubscriptionMsg.value).toMatchObject({ + action: "subscribe", + coValueID: map.coValue.id, + }); + + const mapNewContentMsg = await from1.read(); + expect(mapNewContentMsg.value).toEqual({ + action: "newContent", + coValueID: map.coValue.id, + header: map.coValue.header, + newContent: {}, + } satisfies SyncMessage); + + await to2.write(mapSubscriptionMsg.value!); + + const mapTellKnownStateMsg = await from2.read(); + expect(mapTellKnownStateMsg.value).toEqual({ + action: "tellKnownState", + coValueID: map.coValue.id, + header: false, + sessions: {}, + } satisfies SyncMessage); expect(node2.coValues[map.coValue.id]?.state).toEqual("loading"); - await writer2.write(mapEditMsg.value); + await to2.write(mapNewContentMsg.value!); + + map.edit((editable) => { + editable.set("hello", "world", "trusting"); + }); + + const mapEditMsg = await from1.read(); + + await to2.write(mapEditMsg.value!); await new Promise((resolve) => setTimeout(resolve, 100)); @@ -778,7 +860,8 @@ test("When replaying creation and transactions of a coValue as new content, the ).toEqual("world"); }); -test("When loading a coValue on one node, the server node it is requested from replies with all the necessary depended on coValues to make it work", async () => { +test.skip("When loading a coValue on one node, the server node it is requested from replies with all the necessary depended on coValues to make it work", async () => { + // TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work const admin = newRandomAgentCredential(); const adminID = getAgentID(getAgent(admin)); @@ -793,10 +876,10 @@ test("When loading a coValue on one node, the server node it is requested from r const node2 = new LocalNode(admin, newRandomSessionID(adminID)); - const [node2asPeer, node1asPeer] = connectedPeers(); + const [node1asPeer, node2asPeer] = connectedPeers("peer1", "peer2"); - node1.addPeer(node2asPeer); - node2.addPeer(node1asPeer); + node1.sync.addPeer(node2asPeer); + node2.sync.addPeer(node1asPeer); await node2.loadCoValue(map.coValue.id); @@ -807,6 +890,76 @@ test("When loading a coValue on one node, the server node it is requested from r ).toEqual("world"); }); +test("Can sync a coValue through a server to another client", async () => { + const admin = newRandomAgentCredential(); + const adminID = getAgentID(getAgent(admin)); + + const client1 = new LocalNode(admin, newRandomSessionID(adminID)); + + const team = client1.createTeam(); + + const map = team.createMap(); + map.edit((editable) => { + editable.set("hello", "world", "trusting"); + }); + + const server = new LocalNode(admin, newRandomSessionID(adminID)); + + const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", { + trace: true, + peer1role: "server", + peer2role: "client", + }); + + client1.sync.addPeer(serverAsPeer); + server.sync.addPeer(client1AsPeer); + + const client2 = new LocalNode(admin, newRandomSessionID(adminID)); + + const [serverAsOtherPeer, client2AsPeer] = connectedPeers( + "server", + "client2", + { trace: true, peer1role: "server", peer2role: "client" } + ); + + client2.sync.addPeer(serverAsOtherPeer); + server.sync.addPeer(client2AsPeer); + + const mapOnClient2 = await client2.loadCoValue(map.coValue.id); + + expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual( + "world" + ); +}); + +function teamContentEx(team: Team) { + return { + action: "newContent", + coValueID: team.teamMap.coValue.id, + }; +} + +function admContEx(adminID: AgentID) { + return { + action: "newContent", + coValueID: agentIDAsCoValueID(adminID), + }; +} + +function teamStateEx(team: Team) { + return { + action: "tellKnownState", + coValueID: team.teamMap.coValue.id, + }; +} + +function admStateEx(adminID: AgentID) { + return { + action: "tellKnownState", + coValueID: agentIDAsCoValueID(adminID), + }; +} + function newStreamPair(): [ReadableStream, WritableStream] { const queue: T[] = []; let resolveNextItemReady: () => void = () => {}; @@ -816,9 +969,11 @@ function newStreamPair(): [ReadableStream, WritableStream] { const readable = new ReadableStream({ async pull(controller) { - while (true) { + let retriesLeft = 3; + while (retriesLeft > 0) { + retriesLeft--; if (queue.length > 0) { - controller.enqueue(queue.shift()); + controller.enqueue(queue.shift()!); if (queue.length === 0) { nextItemReady = new Promise((resolve) => { resolveNextItemReady = resolve; @@ -829,6 +984,7 @@ function newStreamPair(): [ReadableStream, WritableStream] { await nextItemReady; } } + throw new Error("Should only use one retry to get next item in queue.") }, }); @@ -836,7 +992,8 @@ function newStreamPair(): [ReadableStream, WritableStream] { write(chunk) { queue.push(chunk); if (queue.length === 1) { - resolveNextItemReady(); + // make sure that await write resolves before corresponding read + process.nextTick(() => resolveNextItemReady()); } }, }); @@ -844,43 +1001,63 @@ function newStreamPair(): [ReadableStream, WritableStream] { return [readable, writable]; } -function shouldNotResolve(promise: Promise, ops: { timeout: number }) { +function shouldNotResolve(promise: Promise, ops: { timeout: number }): Promise { return new Promise((resolve, reject) => { - promise.then((v) => - reject( - new Error( - "Should not have resolved, but resolved to " + - JSON.stringify(v) + promise + .then((v) => + reject( + new Error( + "Should not have resolved, but resolved to " + + JSON.stringify(v) + ) ) ) - ); + .catch(reject); setTimeout(resolve, ops.timeout); }); } -function connectedPeers(trace?: boolean): [Peer, Peer] { +function connectedPeers( + peer1id: PeerID, + peer2id: PeerID, + { + trace = false, + peer1role = "peer", + peer2role = "peer", + }: { + trace?: boolean; + peer1role?: Peer["role"]; + peer2role?: Peer["role"]; + } = {} +): [Peer, Peer] { const [inRx1, inTx1] = newStreamPair(); const [outRx1, outTx1] = newStreamPair(); const [inRx2, inTx2] = newStreamPair(); const [outRx2, outTx2] = newStreamPair(); - outRx2 + void outRx2 .pipeThrough( new TransformStream({ - transform(chunk, controller) { - trace && console.log("peer 2 -> peer 1", chunk); + transform( + chunk: SyncMessage, + controller: { enqueue: (msg: SyncMessage) => void } + ) { + trace && console.log(`${peer2id} -> ${peer1id}`, chunk); controller.enqueue(chunk); }, }) ) .pipeTo(inTx1); - outRx1 + void outRx1 .pipeThrough( new TransformStream({ - transform(chunk, controller) { - trace && console.log("peer 1 -> peer 2", chunk); + transform( + chunk: SyncMessage, + controller: { enqueue: (msg: SyncMessage) => void } + ) { + trace && console.log(`${peer1id} -> ${peer2id}`, chunk); controller.enqueue(chunk); }, }) @@ -888,18 +1065,18 @@ function connectedPeers(trace?: boolean): [Peer, Peer] { .pipeTo(inTx2); const peer2AsPeer: Peer = { - id: "test2", + id: peer2id, incoming: inRx1, outgoing: outTx1, - role: "peer", + role: peer2role, }; const peer1AsPeer: Peer = { - id: "test1", + id: peer1id, incoming: inRx2, outgoing: outTx2, - role: "peer", + role: peer1role, }; - return [peer2AsPeer, peer1AsPeer]; + return [peer1AsPeer, peer2AsPeer]; } diff --git a/src/sync.ts b/src/sync.ts index 3a8bd02e3..72ee1aec2 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,5 +1,9 @@ import { Hash, Signature } from "./crypto"; import { CoValueHeader, RawCoValueID, SessionID, Transaction } from "./coValue"; +import { CoValue } from "./coValue"; +import { LocalNode } from "./node"; +import { newLoadingState } from "./node"; +import { ReadableStream, WritableStream, WritableStreamDefaultWriter } from "isomorphic-streams"; export type CoValueKnownState = { coValueID: RawCoValueID; @@ -7,23 +11,29 @@ export type CoValueKnownState = { sessions: { [sessionID: SessionID]: number }; }; +export function emptyKnownState(coValueID: RawCoValueID): CoValueKnownState { + return { + coValueID, + header: false, + sessions: {}, + }; +} + export type SyncMessage = | SubscribeMessage - | SubscribeResponseMessage + | TellKnownStateMessage | NewContentMessage | WrongAssumedKnownStateMessage | UnsubscribeMessage; export type SubscribeMessage = { action: "subscribe"; - knownState: CoValueKnownState; -}; +} & CoValueKnownState; -export type SubscribeResponseMessage = { - action: "subscribeResponse"; - knownState: CoValueKnownState; +export type TellKnownStateMessage = { + action: "tellKnownState"; asDependencyOf?: RawCoValueID; -}; +} & CoValueKnownState; export type NewContentMessage = { action: "newContent"; @@ -44,8 +54,7 @@ export type SessionNewContent = { export type WrongAssumedKnownStateMessage = { action: "wrongAssumedKnownState"; - knownState: CoValueKnownState; -}; +} & CoValueKnownState; export type UnsubscribeMessage = { action: "unsubscribe"; @@ -64,6 +73,7 @@ export interface Peer { export interface PeerState { id: PeerID; optimisticKnownStates: { [coValueID: RawCoValueID]: CoValueKnownState }; + toldKnownState: Set; incoming: ReadableStream; outgoing: WritableStreamDefaultWriter; role: "peer" | "server" | "client"; @@ -94,10 +104,16 @@ export function weAreStrictlyAhead( return true; } -export function combinedKnownStates(stateA: CoValueKnownState, stateB: CoValueKnownState): CoValueKnownState { +export function combinedKnownStates( + stateA: CoValueKnownState, + stateB: CoValueKnownState +): CoValueKnownState { const sessionStates: CoValueKnownState["sessions"] = {}; - const allSessions = new Set([...Object.keys(stateA.sessions), ...Object.keys(stateB.sessions)] as SessionID[]); + const allSessions = new Set([ + ...Object.keys(stateA.sessions), + ...Object.keys(stateB.sessions), + ] as SessionID[]); for (const sessionID of allSessions) { const stateAValue = stateA.sessions[sessionID]; @@ -111,4 +127,386 @@ export function combinedKnownStates(stateA: CoValueKnownState, stateB: CoValueKn header: stateA.header || stateB.header, sessions: sessionStates, }; -} \ No newline at end of file +} + +export class SyncManager { + peers: { [key: PeerID]: PeerState } = {}; + local: LocalNode; + + constructor(local: LocalNode) { + this.local = local; + } + + loadFromPeers(id: RawCoValueID) { + for (const peer of Object.values(this.peers)) { + peer.outgoing + .write({ + action: "subscribe", + coValueID: id, + header: false, + sessions: {}, + }) + .catch((e) => { + console.error("Error writing to peer", e); + }); + } + } + + async handleSyncMessage(msg: SyncMessage, peer: PeerState) { + // TODO: validate + switch (msg.action) { + case "subscribe": + return await this.handleSubscribe(msg, peer); + case "tellKnownState": + return await this.handleTellKnownState(msg, peer); + case "newContent": + return await this.handleNewContent(msg, peer); + case "wrongAssumedKnownState": + return await this.handleWrongAssumedKnownState(msg, peer); + case "unsubscribe": + return await this.handleUnsubscribe(msg); + default: + throw new Error( + `Unknown message type ${ + (msg as { action: "string" }).action + }` + ); + } + } + + async subscribeToIncludingDependencies( + coValueID: RawCoValueID, + peer: PeerState + ) { + const coValue = this.local.expectCoValueLoaded(coValueID); + + for (const coValueID of coValue.getDependedOnCoValues()) { + await this.subscribeToIncludingDependencies(coValueID, peer); + } + + if (!peer.toldKnownState.has(coValueID)) { + peer.toldKnownState.add(coValueID); + await peer.outgoing.write({ + action: "subscribe", + ...coValue.knownState(), + }); + } + } + + async tellUntoldKnownStateIncludingDependencies( + coValueID: RawCoValueID, + peer: PeerState, + asDependencyOf?: RawCoValueID + ) { + const coValue = this.local.expectCoValueLoaded(coValueID); + + for (const dependentCoValueID of coValue.getDependedOnCoValues()) { + await this.tellUntoldKnownStateIncludingDependencies( + dependentCoValueID, + peer, + asDependencyOf || coValueID + ); + } + + if (!peer.toldKnownState.has(coValueID)) { + await peer.outgoing.write({ + action: "tellKnownState", + asDependencyOf, + ...coValue.knownState(), + }); + + peer.toldKnownState.add(coValueID); + } + } + + async sendNewContentIncludingDependencies( + coValueID: RawCoValueID, + peer: PeerState + ) { + const coValue = this.local.expectCoValueLoaded(coValueID); + + for (const coValueID of coValue.getDependedOnCoValues()) { + await this.sendNewContentIncludingDependencies(coValueID, peer); + } + + const newContent = coValue.newContentSince( + peer.optimisticKnownStates[coValueID] + ); + + if (newContent) { + await peer.outgoing.write(newContent); + peer.optimisticKnownStates[coValueID] = combinedKnownStates( + peer.optimisticKnownStates[coValueID] || + emptyKnownState(coValueID), + coValue.knownState() + ); + } + } + + addPeer(peer: Peer) { + const peerState: PeerState = { + id: peer.id, + optimisticKnownStates: {}, + incoming: peer.incoming, + outgoing: peer.outgoing.getWriter(), + toldKnownState: new Set(), + role: peer.role, + }; + this.peers[peer.id] = peerState; + + if (peer.role === "server") { + const initialSync = async () => { + for (const entry of Object.values(this.local.coValues)) { + if (entry.state === "loading") { + continue; + } + + await this.subscribeToIncludingDependencies( + entry.coValue.id, + peerState + ); + + peerState.optimisticKnownStates[entry.coValue.id] = { + coValueID: entry.coValue.id, + header: false, + sessions: {}, + }; + } + }; + void initialSync(); + } + + const readIncoming = async () => { + for await (const msg of peerState.incoming) { + try { + await this.handleSyncMessage(msg, peerState); + } catch (e) { + console.error( + `Error reading from peer ${peer.id}`, + JSON.stringify(msg), + e + ); + } + } + }; + + void readIncoming(); + } + + async handleSubscribe(msg: SubscribeMessage, peer: PeerState) { + const entry = this.local.coValues[msg.coValueID]; + + if (!entry || entry.state === "loading") { + if (!entry) { + this.local.coValues[msg.coValueID] = newLoadingState(); + } + + peer.optimisticKnownStates[msg.coValueID] = knownStateIn(msg); + peer.toldKnownState.add(msg.coValueID); + + await peer.outgoing.write({ + action: "tellKnownState", + coValueID: msg.coValueID, + header: false, + sessions: {}, + }); + + return; + } + + peer.optimisticKnownStates[msg.coValueID] = knownStateIn(msg); + + await this.tellUntoldKnownStateIncludingDependencies( + msg.coValueID, + peer + ); + + await this.sendNewContentIncludingDependencies(msg.coValueID, peer); + } + + async handleTellKnownState(msg: TellKnownStateMessage, peer: PeerState) { + let entry = this.local.coValues[msg.coValueID]; + + peer.optimisticKnownStates[msg.coValueID] = combinedKnownStates( + peer.optimisticKnownStates[msg.coValueID] || emptyKnownState(msg.coValueID), + knownStateIn(msg) + ); + + if (!entry) { + if (msg.asDependencyOf) { + if (this.local.coValues[msg.asDependencyOf]) { + entry = newLoadingState(); + + this.local.coValues[msg.coValueID] = entry; + } else { + throw new Error( + "Expected coValue dependency entry to be created, missing subscribe?" + ); + } + } else { + throw new Error( + "Expected coValue entry to be created, missing subscribe?" + ); + } + } + + if (entry.state === "loading") { + return []; + } + + await this.tellUntoldKnownStateIncludingDependencies( + msg.coValueID, + peer + ); + await this.sendNewContentIncludingDependencies(msg.coValueID, peer); + } + + async handleNewContent(msg: NewContentMessage, peer: PeerState) { + let entry = this.local.coValues[msg.coValueID]; + + if (!entry) { + throw new Error( + "Expected coValue entry to be created, missing subscribe?" + ); + } + + let resolveAfterDone: ((coValue: CoValue) => void) | undefined; + + const peerOptimisticKnownState = + peer.optimisticKnownStates[msg.coValueID]; + + if (!peerOptimisticKnownState) { + throw new Error( + "Expected optimisticKnownState to be set for coValue we receive new content for" + ); + } + + if (entry.state === "loading") { + if (!msg.header) { + throw new Error("Expected header to be sent in first message"); + } + + peerOptimisticKnownState.header = true; + + const coValue = new CoValue(msg.header, this.local); + + resolveAfterDone = entry.resolve; + + entry = { + state: "loaded", + coValue: coValue, + }; + + this.local.coValues[msg.coValueID] = entry; + } + + const coValue = entry.coValue; + + let invalidStateAssumed = false; + + for (const [sessionID, newContentForSession] of Object.entries( + msg.newContent + ) as [SessionID, SessionNewContent][]) { + const ourKnownTxIdx = + coValue.sessions[sessionID]?.transactions.length; + const theirFirstNewTxIdx = newContentForSession.after; + + if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) { + invalidStateAssumed = true; + continue; + } + + const alreadyKnownOffset = ourKnownTxIdx + ? ourKnownTxIdx - theirFirstNewTxIdx + : 0; + + const newTransactions = + newContentForSession.newTransactions.slice(alreadyKnownOffset); + + const success = coValue.tryAddTransactions( + sessionID, + newTransactions, + newContentForSession.lastHash, + newContentForSession.lastSignature + ); + + if (!success) { + console.error("Failed to add transactions", newTransactions); + continue; + } + + peerOptimisticKnownState.sessions[sessionID] = + newContentForSession.after + + newContentForSession.newTransactions.length; + } + + if (resolveAfterDone) { + resolveAfterDone(coValue); + } + + await this.syncCoValue(coValue); + + if (invalidStateAssumed) { + await peer.outgoing.write({ + action: "wrongAssumedKnownState", + ...coValue.knownState(), + }); + } + } + + async handleWrongAssumedKnownState( + msg: WrongAssumedKnownStateMessage, + peer: PeerState + ) { + const coValue = this.local.expectCoValueLoaded(msg.coValueID); + + peer.optimisticKnownStates[msg.coValueID] = combinedKnownStates( + msg, + coValue.knownState() + ); + + const newContent = coValue.newContentSince(msg); + + if (newContent) { + await peer.outgoing.write(newContent); + } + } + + handleUnsubscribe(_msg: UnsubscribeMessage) { + throw new Error("Method not implemented."); + } + + async syncCoValue(coValue: CoValue) { + for (const peer of Object.values(this.peers)) { + const optimisticKnownState = peer.optimisticKnownStates[coValue.id]; + + const shouldSync = + optimisticKnownState || + peer.role === "server"; + + if (shouldSync) { + await this.tellUntoldKnownStateIncludingDependencies( + coValue.id, + peer + ); + await this.sendNewContentIncludingDependencies( + coValue.id, + peer + ); + } + } + } +} + +function knownStateIn( + msg: + | SubscribeMessage + | TellKnownStateMessage + | WrongAssumedKnownStateMessage +) { + return { + coValueID: msg.coValueID, + header: msg.header, + sessions: msg.sessions, + }; +} diff --git a/tsconfig.json b/tsconfig.json index 91aa71fb6..a255db408 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,10 +14,8 @@ "forceConsistentCasingInFileNames": true, "allowJs": true, "noEmit": true, - "types": [ - "bun-types" // add Bun global - ], - - // "noUncheckedIndexedAccess": true - } + "noUncheckedIndexedAccess": true, + "esModuleInterop": true, + }, + "include": ["./src/**/*"], } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..51df233c8 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2781 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + dependencies: + "@babel/highlight" "^7.22.5" + +"@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" + integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helpers" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.8" + "@babel/types" "^7.22.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.1" + +"@babel/generator@^7.22.7", "@babel/generator@^7.22.9", "@babel/generator@^7.7.2": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== + dependencies: + "@babel/types" "^7.22.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== + dependencies: + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.5" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + dependencies: + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== + +"@babel/helper-validator-option@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== + +"@babel/helpers@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" + integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== + dependencies: + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.6" + "@babel/types" "^7.22.5" + +"@babel/highlight@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" + integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== + dependencies: + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": + version "7.22.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" + integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/template@^7.22.5", "@babel/template@^7.3.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": + version "7.22.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" + integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.7" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/types" "^7.22.5" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" + integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== + +"@eslint/eslintrc@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93" + integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@^8.46.0": + version "8.46.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6" + integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== + +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.2.tgz#bf1d4101347c23e07c029a1b1ae07d550f5cc541" + integrity sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + slash "^3.0.0" + +"@jest/core@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.2.tgz#6f2d1dbe8aa0265fcd4fb8082ae1952f148209c8" + integrity sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg== + dependencies: + "@jest/console" "^29.6.2" + "@jest/reporters" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.5.0" + jest-config "^29.6.2" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-resolve-dependencies "^29.6.2" + jest-runner "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + jest-watcher "^29.6.2" + micromatch "^4.0.4" + pretty-format "^29.6.2" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9" + integrity sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q== + dependencies: + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + jest-mock "^29.6.2" + +"@jest/expect-utils@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534" + integrity sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg== + dependencies: + jest-get-type "^29.4.3" + +"@jest/expect@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.2.tgz#5a2ad58bb345165d9ce0a1845bbf873c480a4b28" + integrity sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg== + dependencies: + expect "^29.6.2" + jest-snapshot "^29.6.2" + +"@jest/fake-timers@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4" + integrity sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA== + dependencies: + "@jest/types" "^29.6.1" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-util "^29.6.2" + +"@jest/globals@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.2.tgz#74af81b9249122cc46f1eb25793617eec69bf21a" + integrity sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/types" "^29.6.1" + jest-mock "^29.6.2" + +"@jest/reporters@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.2.tgz#524afe1d76da33d31309c2c4a2c8062d0c48780a" + integrity sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + jest-worker "^29.6.2" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" + integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" + integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.2.tgz#fdd11583cd1608e4db3114e8f0cce277bf7a32ed" + integrity sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw== + dependencies: + "@jest/console" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz#585eff07a68dd75225a7eacf319780cb9f6b9bf4" + integrity sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw== + dependencies: + "@jest/test-result" "^29.6.2" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.2" + slash "^3.0.0" + +"@jest/transform@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.2.tgz#522901ebbb211af08835bc3bcdf765ab778094e3" + integrity sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.2" + jest-regex-util "^29.4.3" + jest-util "^29.6.2" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" + integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== + dependencies: + "@jest/schemas" "^29.6.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@noble/ciphers@^0.1.3": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.1.4.tgz#96327dca147829ed9eee0d96cfdf7c57915765f0" + integrity sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ== + +"@noble/curves@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== + dependencies: + "@noble/hashes" "1.3.1" + +"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@scure/base@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@types/babel__core@^7.1.14": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" + integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" + integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/graceful-fs@^4.1.3": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.3": + version "29.5.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.3.tgz#7a35dc0044ffb8b56325c6802a4781a626b05777" + integrity sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.12": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + +"@types/node@*": + version "20.4.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.8.tgz#b5dda19adaa473a9bf0ab5cbd8f30ec7d43f5c85" + integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg== + +"@types/semver@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.2.1.tgz#41b79923fee46a745a3a50cba1c33c622aa3c79a" + integrity sha512-iZVM/ALid9kO0+I81pnp1xmYiFyqibAHzrqX4q5YvvVEyJqY+e6rfTXSCsc2jUxGNqJqTfFSSij/NFkZBiBzLw== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.2.1" + "@typescript-eslint/type-utils" "6.2.1" + "@typescript-eslint/utils" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + natural-compare-lite "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.2.1.tgz#e18a31eea1cca8841a565f1701960c8123ed07f9" + integrity sha512-Ld+uL1kYFU8e6btqBFpsHkwQ35rw30IWpdQxgOqOh4NfxSDH6uCkah1ks8R/RgQqI5hHPXMaLy9fbFseIe+dIg== + dependencies: + "@typescript-eslint/scope-manager" "6.2.1" + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/typescript-estree" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.2.1.tgz#b6f43a867b84e5671fe531f2b762e0b68f7cf0c4" + integrity sha512-UCqBF9WFqv64xNsIEPfBtenbfodPXsJ3nPAr55mGPkQIkiQvgoWNo+astj9ZUfJfVKiYgAZDMnM6dIpsxUMp3Q== + dependencies: + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + +"@typescript-eslint/type-utils@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.2.1.tgz#8eb8a2cccdf39cd7cf93e02bd2c3782dc90b0525" + integrity sha512-fTfCgomBMIgu2Dh2Or3gMYgoNAnQm3RLtRp+jP7A8fY+LJ2+9PNpi5p6QB5C4RSP+U3cjI0vDlI3mspAkpPVbQ== + dependencies: + "@typescript-eslint/typescript-estree" "6.2.1" + "@typescript-eslint/utils" "6.2.1" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.2.1.tgz#7fcdeceb503aab601274bf5e210207050d88c8ab" + integrity sha512-528bGcoelrpw+sETlyM91k51Arl2ajbNT9L4JwoXE2dvRe1yd8Q64E4OL7vHYw31mlnVsf+BeeLyAZUEQtqahQ== + +"@typescript-eslint/typescript-estree@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.1.tgz#2af6e90c1e91cb725a5fe1682841a3f74549389e" + integrity sha512-G+UJeQx9AKBHRQBpmvr8T/3K5bJa485eu+4tQBxFq0KoT22+jJyzo1B50JDT9QdC1DEmWQfdKsa8ybiNWYsi0Q== + dependencies: + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/visitor-keys" "6.2.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.2.1.tgz#2aa4279ec13053d05615bcbde2398e1e8f08c334" + integrity sha512-eBIXQeupYmxVB6S7x+B9SdBeB6qIdXKjgQBge2J+Ouv8h9Cxm5dHf/gfAZA6dkMaag+03HdbVInuXMmqFB/lKQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.2.1" + "@typescript-eslint/types" "6.2.1" + "@typescript-eslint/typescript-estree" "6.2.1" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.1.tgz#442e7c09fe94b715a54ebe30e967987c3c41fbf4" + integrity sha512-iTN6w3k2JEZ7cyVdZJTVJx2Lv7t6zFA8DCrJEHD2mwfc16AEvvBWVhbFh34XyG2NORCd0viIgQY1+u7kPI0WpA== + dependencies: + "@typescript-eslint/types" "6.2.1" + eslint-visitor-keys "^3.4.1" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +babel-jest@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.2.tgz#cada0a59e07f5acaeb11cbae7e3ba92aec9c1126" + integrity sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A== + dependencies: + "@jest/transform" "^29.6.2" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.5.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== + dependencies: + babel-plugin-jest-hoist "^29.5.0" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.21.9: + version "4.21.10" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" + integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== + dependencies: + caniuse-lite "^1.0.30001517" + electron-to-chromium "^1.4.477" + node-releases "^2.0.13" + update-browserslist-db "^1.0.11" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001517: + version "1.0.30001519" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz#3e7b8b8a7077e78b0eb054d69e6edf5c7df35601" + integrity sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== + +cjs-module-lexer@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +electron-to-chromium@^1.4.477: + version "1.4.485" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.485.tgz#fde3ee9ee8112a3414c0dfa545385ad08ec43408" + integrity sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f" + integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw== + +eslint@^8.46.0: + version "8.46.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552" + integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.1" + "@eslint/js" "^8.46.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.2" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521" + integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== + dependencies: + "@jest/expect-utils" "^29.6.2" + "@types/node" "*" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +"isomorphic-streams@https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae": + version "1.0.3" + resolved "https://github.com/sgwilym/isomorphic-streams.git#aa9394781bfc92f8d7c981be7daf8af4b4cd4fae" + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.2.tgz#1e6ffca60151ac66cad63fce34f443f6b5bb4258" + integrity sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.6.2" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" + p-limit "^3.1.0" + pretty-format "^29.6.2" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.2.tgz#edb381763398d1a292cd1b636a98bfa5644b8fda" + integrity sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q== + dependencies: + "@jest/core" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.2.tgz#c68723f06b31ca5e63030686e604727d406cd7c3" + integrity sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.6.2" + "@jest/types" "^29.6.1" + babel-jest "^29.6.2" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.6.2" + jest-environment-node "^29.6.2" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-runner "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.6.2" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" + integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.6.2" + +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.2.tgz#c9e4b340bcbe838c73adf46b76817b15712d02ce" + integrity sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw== + dependencies: + "@jest/types" "^29.6.1" + chalk "^4.0.0" + jest-get-type "^29.4.3" + jest-util "^29.6.2" + pretty-format "^29.6.2" + +jest-environment-node@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.2.tgz#a9ea2cabff39b08eca14ccb32c8ceb924c8bb1ad" + integrity sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + jest-mock "^29.6.2" + jest-util "^29.6.2" + +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== + +jest-haste-map@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.2.tgz#298c25ea5255cfad8b723179d4295cf3a50a70d1" + integrity sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA== + dependencies: + "@jest/types" "^29.6.1" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.4.3" + jest-util "^29.6.2" + jest-worker "^29.6.2" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz#e2b307fee78cab091c37858a98c7e1d73cdf5b38" + integrity sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ== + dependencies: + jest-get-type "^29.4.3" + pretty-format "^29.6.2" + +jest-matcher-utils@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535" + integrity sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ== + dependencies: + chalk "^4.0.0" + jest-diff "^29.6.2" + jest-get-type "^29.4.3" + pretty-format "^29.6.2" + +jest-message-util@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb" + integrity sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.6.2" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00" + integrity sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + jest-util "^29.6.2" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== + +jest-resolve-dependencies@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz#36435269b6672c256bcc85fb384872c134cc4cf2" + integrity sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w== + dependencies: + jest-regex-util "^29.4.3" + jest-snapshot "^29.6.2" + +jest-resolve@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.2.tgz#f18405fe4b50159b7b6d85e81f6a524d22afb838" + integrity sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.2" + jest-pnp-resolver "^1.2.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.2.tgz#89e8e32a8fef24781a7c4c49cd1cb6358ac7fc01" + integrity sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w== + dependencies: + "@jest/console" "^29.6.2" + "@jest/environment" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.4.3" + jest-environment-node "^29.6.2" + jest-haste-map "^29.6.2" + jest-leak-detector "^29.6.2" + jest-message-util "^29.6.2" + jest-resolve "^29.6.2" + jest-runtime "^29.6.2" + jest-util "^29.6.2" + jest-watcher "^29.6.2" + jest-worker "^29.6.2" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.2.tgz#692f25e387f982e89ab83270e684a9786248e545" + integrity sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/globals" "^29.6.2" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.2.tgz#9b431b561a83f2bdfe041e1cab8a6becdb01af9c" + integrity sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.6.2" + graceful-fs "^4.2.9" + jest-diff "^29.6.2" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + natural-compare "^1.4.0" + pretty-format "^29.6.2" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" + integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082" + integrity sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg== + dependencies: + "@jest/types" "^29.6.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.4.3" + leven "^3.1.0" + pretty-format "^29.6.2" + +jest-watcher@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.2.tgz#77c224674f0620d9f6643c4cfca186d8893ca088" + integrity sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA== + dependencies: + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.6.2" + string-length "^4.0.1" + +jest-worker@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.2.tgz#682fbc4b6856ad0aa122a5403c6d048b83f3fb44" + integrity sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ== + dependencies: + "@types/node" "*" + jest-util "^29.6.2" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.2.tgz#3bd55b9fd46a161b2edbdf5f1d1bd0d1eab76c42" + integrity sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg== + dependencies: + "@jest/core" "^29.6.2" + "@jest/types" "^29.6.1" + import-local "^3.0.2" + jest-cli "^29.6.2" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" + integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== + dependencies: + "@jest/schemas" "^29.6.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +pure-rand@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.20.0: + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" + integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== + +ts-jest@^29.1.1: + version "29.1.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" + integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" + integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== + +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-to-istanbul@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 8578d0f2fc173a98d536775ccaa71462b85fbc1c Mon Sep 17 00:00:00 2001 From: Anselm Date: Mon, 7 Aug 2023 17:52:58 +0100 Subject: [PATCH 2/4] Introduce coValue nicknames and change how agentIDs work --- src/coValue.test.ts | 12 +++--- src/coValue.ts | 36 +++++++++-------- src/contentType.test.ts | 10 ++--- src/crypto.test.ts | 36 ++++++++--------- src/node.ts | 1 + src/permissions.test.ts | 75 ++++++++++++++++++++--------------- src/permissions.ts | 3 +- src/sync.test.ts | 88 +++++++++++++++++++++++++++++++---------- 8 files changed, 161 insertions(+), 100 deletions(-) diff --git a/src/coValue.test.ts b/src/coValue.test.ts index fc78fba8c..618eaaaea 100644 --- a/src/coValue.test.ts +++ b/src/coValue.test.ts @@ -10,7 +10,7 @@ import { LocalNode } from "./node"; import { sign } from "./crypto"; test("Can create coValue with new agent credentials and add transaction to it", () => { - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -48,9 +48,8 @@ test("Can create coValue with new agent credentials and add transaction to it", }); test("transactions with wrong signature are rejected", () => { - const agent = newRandomAgentCredential(); - const wrongAgent = newRandomAgentCredential(); - const agentCredential = newRandomAgentCredential(); + const wrongAgent = newRandomAgentCredential("wrongAgent"); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -88,8 +87,7 @@ test("transactions with wrong signature are rejected", () => { }); test("transactions with correctly signed, but wrong hash are rejected", () => { - const agent = newRandomAgentCredential(); - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -131,7 +129,7 @@ test("transactions with correctly signed, but wrong hash are rejected", () => { node.ownSessionID, [transaction], expectedNewHash, - sign(agent.signatorySecret, expectedNewHash) + sign(agentCredential.signatorySecret, expectedNewHash) ) ).toBe(false); }); diff --git a/src/coValue.ts b/src/coValue.ts index a6edc8753..5b2093de7 100644 --- a/src/coValue.ts +++ b/src/coValue.ts @@ -33,27 +33,32 @@ import { import { LocalNode } from "./node"; import { CoValueKnownState, NewContentMessage } from "./sync"; -export type RawCoValueID = `coval_${string}`; +export type RawCoValueID = `co_z${string}` | `co_${string}_z${string}`; export type CoValueHeader = { type: ContentType["type"]; ruleset: RulesetDef; meta: JsonValue; + publicNickname?: string; }; function coValueIDforHeader(header: CoValueHeader): RawCoValueID { const hash = shortHash(header); - return `coval_${hash.slice("shortHash_".length)}`; + if (header.publicNickname) { + return `co_${header.publicNickname}_z${hash.slice("shortHash_z".length)}`; + } else { + return `co_z${hash.slice("shortHash_z".length)}`; + } } -export type SessionID = `session_${string}_${AgentID}`; +export type SessionID = `${AgentID}_session_z${string}`; export function agentIDfromSessionID(sessionID: SessionID): AgentID { - return `agent_${sessionID.substring(sessionID.lastIndexOf("_") + 1)}`; + return sessionID.split("_session")[0] as AgentID; } export function newRandomSessionID(agentID: AgentID): SessionID { - return `session_${base58.encode(randomBytes(8))}_${agentID}`; + return `${agentID}_session_z${base58.encode(randomBytes(8))}`; } type SessionLog = { @@ -482,25 +487,26 @@ export class CoValue { return this.header.ruleset.type === "team" ? expectTeamContent(this.getCurrentContent()) .keys() - .filter((k): k is AgentID => k.startsWith("agent_")) - .map((agent) => agentIDAsCoValueID(agent)) + .filter((k): k is AgentID => k.startsWith("co_agent")) : this.header.ruleset.type === "ownedByTeam" ? [this.header.ruleset.team] : []; } } -export type AgentID = `agent_${string}`; +export type AgentID = `co_agent${string}_z${string}`; export type Agent = { signatoryID: SignatoryID; recipientID: RecipientID; + publicNickname?: string; }; export function getAgent(agentCredential: AgentCredential) { return { signatoryID: getSignatoryID(agentCredential.signatorySecret), recipientID: getRecipientID(agentCredential.recipientSecret), + publicNickname: agentCredential.publicNickname, }; } @@ -513,28 +519,24 @@ export function getAgentCoValueHeader(agent: Agent): CoValueHeader { initialRecipientID: agent.recipientID, }, meta: null, + publicNickname: "agent" + agent.publicNickname?.slice(0, 1).toUpperCase() + agent.publicNickname?.slice(1), }; } export function getAgentID(agent: Agent): AgentID { - return `agent_${coValueIDforHeader(getAgentCoValueHeader(agent)).slice( - "coval_".length - )}`; -} - -export function agentIDAsCoValueID(agentID: AgentID): RawCoValueID { - return `coval_${agentID.substring("agent_".length)}`; + return coValueIDforHeader(getAgentCoValueHeader(agent)) as AgentID; } export type AgentCredential = { signatorySecret: SignatorySecret; recipientSecret: RecipientSecret; + publicNickname?: string; }; -export function newRandomAgentCredential(): AgentCredential { +export function newRandomAgentCredential(publicNickname: string): AgentCredential { const signatorySecret = newRandomSignatory(); const recipientSecret = newRandomRecipient(); - return { signatorySecret, recipientSecret }; + return { signatorySecret, recipientSecret, publicNickname }; } // type Role = "admin" | "writer" | "reader"; diff --git a/src/contentType.test.ts b/src/contentType.test.ts index c1f313a89..53f253e30 100644 --- a/src/contentType.test.ts +++ b/src/contentType.test.ts @@ -8,7 +8,7 @@ import { import { LocalNode } from "./node"; test("Empty COJSON Map works", () => { - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -32,7 +32,7 @@ test("Empty COJSON Map works", () => { }); test("Can insert and delete Map entries in edit()", () => { - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -64,7 +64,7 @@ test("Can insert and delete Map entries in edit()", () => { }); test("Can get map entry values at different points in time", () => { - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -103,7 +103,7 @@ test("Can get map entry values at different points in time", () => { }); test("Can get all historic values of key", () => { - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) @@ -160,7 +160,7 @@ test("Can get all historic values of key", () => { }); test("Can get last tx ID for a key", () => { - const agentCredential = newRandomAgentCredential(); + const agentCredential = newRandomAgentCredential("agent1"); const node = new LocalNode( agentCredential, newRandomSessionID(getAgentID(getAgent(agentCredential))) diff --git a/src/crypto.test.ts b/src/crypto.test.ts index 3226ff6b0..fa04d4695 100644 --- a/src/crypto.test.ts +++ b/src/crypto.test.ts @@ -49,8 +49,8 @@ test("Sealing round-trips, but invalid receiver can't unseal", () => { const recipient3 = newRandomRecipient(); const nOnceMaterial = { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }, } as const; const sealed = seal( @@ -106,23 +106,23 @@ test("Encryption for transactions round-trips", () => { const { secret } = newRandomKeySecret(); const encrypted1 = encryptForTransaction({ a: "hello" }, secret, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }, }); const encrypted2 = encryptForTransaction({ b: "world" }, secret, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }, }); const decrypted1 = decryptForTransaction(encrypted1, secret, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }, }); const decrypted2 = decryptForTransaction(encrypted2, secret, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }, }); expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]); @@ -133,23 +133,23 @@ test("Encryption for transactions doesn't decrypt with a wrong key", () => { const { secret: secret2 } = newRandomKeySecret(); const encrypted1 = encryptForTransaction({ a: "hello" }, secret, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }, }); const encrypted2 = encryptForTransaction({ b: "world" }, secret, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }, }); const decrypted1 = decryptForTransaction(encrypted1, secret2, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 0 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 0 }, }); const decrypted2 = decryptForTransaction(encrypted2, secret2, { - in: "coval_zTEST", - tx: { sessionID: "session_zTEST_agent_zTEST", txIndex: 1 }, + in: "co_zTEST", + tx: { sessionID: "co_agent_zTEST_session_zTEST", txIndex: 1 }, }); expect([decrypted1, decrypted2]).toEqual([undefined, undefined]); diff --git a/src/node.ts b/src/node.ts index df50bd0f9..729171ce5 100644 --- a/src/node.ts +++ b/src/node.ts @@ -89,6 +89,7 @@ export class LocalNode { type: "comap", ruleset: { type: "team", initialAdmin: this.agentID }, meta: null, + publicNickname: "team", }); let teamContent = expectTeamContent(teamCoValue.getCurrentContent()); diff --git a/src/permissions.test.ts b/src/permissions.test.ts index 270c267b0..24742d980 100644 --- a/src/permissions.test.ts +++ b/src/permissions.test.ts @@ -17,7 +17,7 @@ import { function teamWithTwoAdmins() { const { team, admin, adminID } = newTeam(); - const otherAdmin = newRandomAgentCredential(); + const otherAdmin = newRandomAgentCredential("otherAdmin"); const otherAdminID = getAgentID(getAgent(otherAdmin)); let content = expectTeamContent(team.getCurrentContent()); @@ -38,7 +38,7 @@ function teamWithTwoAdmins() { } function newTeam() { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -47,6 +47,7 @@ function newTeam() { type: "comap", ruleset: { type: "team", initialAdmin: adminID }, meta: null, + publicNickname: "team" }); const teamContent = expectTeamContent(team.getCurrentContent()); @@ -64,7 +65,7 @@ test("Initial admin can add another admin to a team", () => { }); function newTeamHighLevel() { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -77,7 +78,7 @@ function newTeamHighLevel() { function teamWithTwoAdminsHighLevel() { const { admin, adminID, node, team } = newTeamHighLevel(); - const otherAdmin = newRandomAgentCredential(); + const otherAdmin = newRandomAgentCredential("otherAdmin"); const otherAdminID = getAgentID(getAgent(otherAdmin)); node.addKnownAgent(getAgent(otherAdmin)); @@ -103,7 +104,7 @@ test("Added admin can add a third admin to a team", () => { expect(otherContent.get(otherAdminID)).toEqual("admin"); - const thirdAdmin = newRandomAgentCredential(); + const thirdAdmin = newRandomAgentCredential("admin"); const thirdAdminID = getAgentID(getAgent(thirdAdmin)); otherContent.edit((editable) => { @@ -125,7 +126,7 @@ test("Added adming can add a third admin to a team (high level)", () => { newRandomSessionID(otherAdminID) ); - const thirdAdmin = newRandomAgentCredential(); + const thirdAdmin = newRandomAgentCredential("admin"); const thirdAdminID = getAgentID(getAgent(thirdAdmin)); node.addKnownAgent(getAgent(thirdAdmin)); @@ -187,7 +188,7 @@ test("Admins can't demote other admins in a team (high level)", () => { test("Admins an add writers to a team, who can't add admins, writers, or readers", () => { const { team } = newTeam(); - const writer = newRandomAgentCredential(); + const writer = newRandomAgentCredential("writer"); const writerID = getAgentID(getAgent(writer)); let teamContent = expectTeamContent(team.getCurrentContent()); @@ -211,7 +212,7 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers expect(teamContentAsWriter.get(writerID)).toEqual("writer"); - const otherAgent = newRandomAgentCredential(); + const otherAgent = newRandomAgentCredential("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); teamContentAsWriter.edit((editable) => { @@ -233,7 +234,7 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers test("Admins an add writers to a team, who can't add admins, writers, or readers (high level)", () => { const { team, node } = newTeamHighLevel(); - const writer = newRandomAgentCredential(); + const writer = newRandomAgentCredential("writer"); const writerID = getAgentID(getAgent(writer)); node.addKnownAgent(getAgent(writer)); @@ -248,7 +249,7 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers expect(teamAsWriter.teamMap.get(writerID)).toEqual("writer"); - const otherAgent = newRandomAgentCredential(); + const otherAgent = newRandomAgentCredential("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); node.addKnownAgent(getAgent(otherAgent)); @@ -268,7 +269,7 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers test("Admins can add readers to a team, who can't add admins, writers, or readers", () => { const { team } = newTeam(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); let teamContent = expectTeamContent(team.getCurrentContent()); @@ -292,7 +293,7 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader expect(teamContentAsReader.get(readerID)).toEqual("reader"); - const otherAgent = newRandomAgentCredential(); + const otherAgent = newRandomAgentCredential("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); teamContentAsReader.edit((editable) => { @@ -314,7 +315,7 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader test("Admins can add readers to a team, who can't add admins, writers, or readers (high level)", () => { const { team, node } = newTeamHighLevel(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); node.addKnownAgent(getAgent(reader)); @@ -329,7 +330,7 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader expect(teamAsReader.teamMap.get(readerID)).toEqual("reader"); - const otherAgent = newRandomAgentCredential(); + const otherAgent = newRandomAgentCredential("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); node.addKnownAgent(getAgent(otherAgent)); @@ -354,6 +355,7 @@ test("Admins can write to an object that is owned by their team", () => { type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); let childContent = expectMap(childObject.getCurrentContent()); @@ -384,7 +386,7 @@ test("Admins can write to an object that is owned by their team (high level)", ( test("Writers can write to an object that is owned by their team", () => { const { node, team } = newTeam(); - const writer = newRandomAgentCredential(); + const writer = newRandomAgentCredential("writer"); const writerID = getAgentID(getAgent(writer)); expectTeamContent(team.getCurrentContent()).edit((editable) => { @@ -396,6 +398,7 @@ test("Writers can write to an object that is owned by their team", () => { type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); const childObjectAsWriter = childObject.testWithDifferentCredentials( @@ -420,7 +423,7 @@ test("Writers can write to an object that is owned by their team", () => { test("Writers can write to an object that is owned by their team (high level)", () => { const { node, team } = newTeamHighLevel(); - const writer = newRandomAgentCredential(); + const writer = newRandomAgentCredential("writer"); const writerID = getAgentID(getAgent(writer)); node.addKnownAgent(getAgent(writer)); @@ -446,7 +449,7 @@ test("Writers can write to an object that is owned by their team (high level)", test("Readers can not write to an object that is owned by their team", () => { const { node, team } = newTeam(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); expectTeamContent(team.getCurrentContent()).edit((editable) => { @@ -458,6 +461,7 @@ test("Readers can not write to an object that is owned by their team", () => { type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); const childObjectAsReader = childObject.testWithDifferentCredentials( @@ -482,7 +486,7 @@ test("Readers can not write to an object that is owned by their team", () => { test("Readers can not write to an object that is owned by their team (high level)", () => { const { node, team } = newTeamHighLevel(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); node.addKnownAgent(getAgent(reader)); @@ -533,6 +537,7 @@ test("Admins can set team read key and then use it to create and read private tr type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); let childContent = expectMap(childObject.getCurrentContent()); @@ -562,7 +567,7 @@ test("Admins can set team read key and then use it to create and read private tr test("Admins can set team read key and then writers can use it to create and read private transactions in owned objects", () => { const { node, team, admin } = newTeam(); - const writer = newRandomAgentCredential(); + const writer = newRandomAgentCredential("writer"); const writerID = getAgentID(getAgent(writer)); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); @@ -591,6 +596,7 @@ test("Admins can set team read key and then writers can use it to create and rea type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); const childObjectAsWriter = childObject.testWithDifferentCredentials( @@ -617,7 +623,7 @@ test("Admins can set team read key and then writers can use it to create and rea test("Admins can set team read key and then writers can use it to create and read private transactions in owned objects (high level)", () => { const { node, team, admin } = newTeamHighLevel(); - const writer = newRandomAgentCredential(); + const writer = newRandomAgentCredential("writer"); const writerID = getAgentID(getAgent(writer)); node.addKnownAgent(getAgent(writer)); @@ -643,7 +649,7 @@ test("Admins can set team read key and then writers can use it to create and rea test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read", () => { const { node, team, admin } = newTeam(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); @@ -672,6 +678,7 @@ test("Admins can set team read key and then use it to create private transaction type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); expectMap(childObject.getCurrentContent()).edit((editable) => { @@ -696,7 +703,7 @@ test("Admins can set team read key and then use it to create private transaction test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read (high level)", () => { const { node, team, admin } = newTeamHighLevel(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); node.addKnownAgent(getAgent(reader)); @@ -722,9 +729,9 @@ test("Admins can set team read key and then use it to create private transaction test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read, even with a separate later revelation for the same read key", () => { const { node, team, admin } = newTeam(); - const reader1 = newRandomAgentCredential(); + const reader1 = newRandomAgentCredential("reader1"); const reader1ID = getAgentID(getAgent(reader1)); - const reader2 = newRandomAgentCredential(); + const reader2 = newRandomAgentCredential("reader2"); const reader2ID = getAgentID(getAgent(reader2)); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); @@ -772,6 +779,7 @@ test("Admins can set team read key and then use it to create private transaction type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); expectMap(childObject.getCurrentContent()).edit((editable) => { @@ -809,9 +817,9 @@ test("Admins can set team read key and then use it to create private transaction test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read, even with a separate later revelation for the same read key (high level)", () => { const { node, team, admin } = newTeamHighLevel(); - const reader1 = newRandomAgentCredential(); + const reader1 = newRandomAgentCredential("reader1"); const reader1ID = getAgentID(getAgent(reader1)); - const reader2 = newRandomAgentCredential(); + const reader2 = newRandomAgentCredential("reader2"); const reader2ID = getAgentID(getAgent(reader2)); node.addKnownAgent(getAgent(reader1)); @@ -872,6 +880,7 @@ test("Admins can set team read key, make a private transaction in an owned objec type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); let childContent = expectMap(childObject.getCurrentContent()); @@ -951,6 +960,7 @@ test("Admins can set team read key, make a private transaction in an owned objec type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); const teamContent = expectTeamContent(team.getCurrentContent()); @@ -984,7 +994,7 @@ test("Admins can set team read key, make a private transaction in an owned objec childContent = expectMap(childObject.getCurrentContent()); expect(childContent.get("foo")).toEqual("bar"); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); const { secret: readKey2, id: readKeyID2 } = newRandomKeySecret(); @@ -1064,7 +1074,7 @@ test("Admins can set team read key, make a private transaction in an owned objec expect(childObject.coValue.getCurrentReadKey()).not.toEqual(firstReadKey); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); node.addKnownAgent(getAgent(reader)); @@ -1093,13 +1103,14 @@ test("Admins can set team read rey, make a private transaction in an owned objec type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "childObject" }); const teamContent = expectTeamContent(team.getCurrentContent()); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); - const reader2 = newRandomAgentCredential(); + const reader2 = newRandomAgentCredential("reader2"); const reader2ID = getAgentID(getAgent(reader)); teamContent.edit((editable) => { @@ -1229,9 +1240,9 @@ test("Admins can set team read rey, make a private transaction in an owned objec const secondReadKey = childObject.coValue.getCurrentReadKey(); - const reader = newRandomAgentCredential(); + const reader = newRandomAgentCredential("reader"); const readerID = getAgentID(getAgent(reader)); - const reader2 = newRandomAgentCredential(); + const reader2 = newRandomAgentCredential("reader2"); const reader2ID = getAgentID(getAgent(reader2)); node.addKnownAgent(getAgent(reader)); diff --git a/src/permissions.ts b/src/permissions.ts index 8f0a0d0bc..214e11942 100644 --- a/src/permissions.ts +++ b/src/permissions.ts @@ -271,7 +271,7 @@ export class Team { rotateReadKey() { const currentlyPermittedReaders = this.teamMap.keys().filter((key) => { - if (key.startsWith("agent_")) { + if (key.startsWith("co_agent")) { const role = this.teamMap.get(key); return ( role === "admin" || role === "writer" || role === "reader" @@ -342,6 +342,7 @@ export class Team { team: this.teamMap.id, }, meta: meta || null, + publicNickname: "map", }) .getCurrentContent() as CoMap; } diff --git a/src/sync.test.ts b/src/sync.test.ts index 4bd41715d..248947761 100644 --- a/src/sync.test.ts +++ b/src/sync.test.ts @@ -1,6 +1,5 @@ import { AgentID, - agentIDAsCoValueID, getAgent, getAgentID, newRandomAgentCredential, @@ -19,7 +18,7 @@ import { test( "Node replies with initial tx and header to empty subscribe", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -74,6 +73,7 @@ test( type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, + publicNickname: "map", }, newContent: { [node.ownSessionID]: { @@ -103,7 +103,7 @@ test( ); test("Node replies with only new tx to subscribe with some known state", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -188,7 +188,7 @@ test.todo( ); test("After subscribing, node sends own known state and new txs to peer", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -309,7 +309,7 @@ test("After subscribing, node sends own known state and new txs to peer", async }); test("Client replies with known new content to tellKnownState from server", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -391,7 +391,7 @@ test("Client replies with known new content to tellKnownState from server", asyn }); test("No matter the optimistic known state, node respects invalid known state messages and resyncs", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -496,7 +496,7 @@ test("No matter the optimistic known state, node respects invalid known state me }); test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -525,7 +525,7 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m }); test("If we add a server peer, all updates to all coValues are sent to it, even if it doesn't subscribe", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -547,7 +547,7 @@ test("If we add a server peer, all updates to all coValues are sent to it, even const reader = outRx.getReader(); expect((await reader.read()).value).toMatchObject({ action: "subscribe", - coValueID: agentIDAsCoValueID(adminID), + coValueID: adminID, }); expect((await reader.read()).value).toMatchObject({ action: "subscribe", @@ -602,7 +602,7 @@ test("If we add a server peer, all updates to all coValues are sent to it, even }); test("If we add a server peer, newly created coValues are auto-subscribed to", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -624,7 +624,7 @@ test("If we add a server peer, newly created coValues are auto-subscribed to", a const reader = outRx.getReader(); expect((await reader.read()).value).toMatchObject({ action: "subscribe", - coValueID: agentIDAsCoValueID(adminID), + coValueID: adminID, }); expect((await reader.read()).value).toMatchObject({ action: "subscribe", @@ -658,7 +658,7 @@ test.todo( ); test("When we connect a new server peer, we try to sync all existing coValues to it", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -696,7 +696,7 @@ test("When we connect a new server peer, we try to sync all existing coValues to }); test("When receiving a subscribe with a known state that is ahead of our own, peers should respond with a corresponding subscribe response message", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node = new LocalNode(admin, newRandomSessionID(adminID)); @@ -740,7 +740,7 @@ test("When receiving a subscribe with a known state that is ahead of our own, pe test.skip("When replaying creation and transactions of a coValue as new content, the receiving peer integrates this information", async () => { // TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node1 = new LocalNode(admin, newRandomSessionID(adminID)); @@ -778,7 +778,7 @@ test.skip("When replaying creation and transactions of a coValue as new content, const adminSubscribeMessage = await from1.read(); expect(adminSubscribeMessage.value).toMatchObject({ action: "subscribe", - coValueID: agentIDAsCoValueID(adminID), + coValueID: adminID, }); const teamSubscribeMsg = await from1.read(); expect(teamSubscribeMsg.value).toMatchObject({ @@ -862,7 +862,7 @@ test.skip("When replaying creation and transactions of a coValue as new content, test.skip("When loading a coValue on one node, the server node it is requested from replies with all the necessary depended on coValues to make it work", async () => { // TODO: this test is mostly correct but also slightly unrealistic, make sure we pass all messages back and forth as expected and then it should work - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const node1 = new LocalNode(admin, newRandomSessionID(adminID)); @@ -891,7 +891,7 @@ test.skip("When loading a coValue on one node, the server node it is requested f }); test("Can sync a coValue through a server to another client", async () => { - const admin = newRandomAgentCredential(); + const admin = newRandomAgentCredential("admin"); const adminID = getAgentID(getAgent(admin)); const client1 = new LocalNode(admin, newRandomSessionID(adminID)); @@ -903,7 +903,55 @@ test("Can sync a coValue through a server to another client", async () => { editable.set("hello", "world", "trusting"); }); - const server = new LocalNode(admin, newRandomSessionID(adminID)); + const serverUser = newRandomAgentCredential("serverUser"); + const serverUserID = getAgentID(getAgent(serverUser)); + + const server = new LocalNode(serverUser, newRandomSessionID(serverUserID)); + + const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", { + trace: true, + peer1role: "server", + peer2role: "client", + }); + + client1.sync.addPeer(serverAsPeer); + server.sync.addPeer(client1AsPeer); + + const client2 = new LocalNode(admin, newRandomSessionID(adminID)); + + const [serverAsOtherPeer, client2AsPeer] = connectedPeers( + "server", + "client2", + { trace: true, peer1role: "server", peer2role: "client" } + ); + + client2.sync.addPeer(serverAsOtherPeer); + server.sync.addPeer(client2AsPeer); + + const mapOnClient2 = await client2.loadCoValue(map.coValue.id); + + expect(expectMap(mapOnClient2.getCurrentContent()).get("hello")).toEqual( + "world" + ); +}); + +test("Can sync a coValue with private transactions through a server to another client", async () => { + const admin = newRandomAgentCredential("admin"); + const adminID = getAgentID(getAgent(admin)); + + const client1 = new LocalNode(admin, newRandomSessionID(adminID)); + + const team = client1.createTeam(); + + const map = team.createMap(); + map.edit((editable) => { + editable.set("hello", "world", "private"); + }); + + const serverUser = newRandomAgentCredential("serverUser"); + const serverUserID = getAgentID(getAgent(serverUser)); + + const server = new LocalNode(serverUser, newRandomSessionID(serverUserID)); const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", { trace: true, @@ -942,7 +990,7 @@ function teamContentEx(team: Team) { function admContEx(adminID: AgentID) { return { action: "newContent", - coValueID: agentIDAsCoValueID(adminID), + coValueID: adminID, }; } @@ -956,7 +1004,7 @@ function teamStateEx(team: Team) { function admStateEx(adminID: AgentID) { return { action: "tellKnownState", - coValueID: agentIDAsCoValueID(adminID), + coValueID: adminID, }; } From 9c357bb5d77b7c68980368e45fe6cfbb8064bb10 Mon Sep 17 00:00:00 2001 From: Anselm Date: Mon, 7 Aug 2023 19:42:02 +0100 Subject: [PATCH 3/4] Better management of locally created agents --- src/coValue.ts | 26 +++--- src/node.ts | 38 +++++--- src/permissions.test.ts | 189 ++++++++++++++++++---------------------- src/permissions.ts | 4 +- 4 files changed, 132 insertions(+), 125 deletions(-) diff --git a/src/coValue.ts b/src/coValue.ts index 5b2093de7..bc3c70b18 100644 --- a/src/coValue.ts +++ b/src/coValue.ts @@ -45,7 +45,9 @@ export type CoValueHeader = { function coValueIDforHeader(header: CoValueHeader): RawCoValueID { const hash = shortHash(header); if (header.publicNickname) { - return `co_${header.publicNickname}_z${hash.slice("shortHash_z".length)}`; + return `co_${header.publicNickname}_z${hash.slice( + "shortHash_z".length + )}`; } else { return `co_z${hash.slice("shortHash_z".length)}`; } @@ -151,8 +153,10 @@ export class CoValue { newHash: Hash, newSignature: Signature ): boolean { - const signatoryID = - this.node.knownAgents[agentIDfromSessionID(sessionID)]?.signatoryID; + const signatoryID = this.node.expectAgentLoaded( + agentIDfromSessionID(sessionID), + "Expected to know signatory of transaction" + ).signatoryID; if (!signatoryID) { console.warn("Unknown agent", agentIDfromSessionID(sessionID)); @@ -362,11 +366,10 @@ export class CoValue { for (const entry of readKeyHistory) { if (entry.value?.keyID === keyID) { const revealer = agentIDfromSessionID(entry.txID.sessionID); - const revealerAgent = this.node.knownAgents[revealer]; - - if (!revealerAgent) { - throw new Error("Unknown revealer"); - } + const revealerAgent = this.node.expectAgentLoaded( + revealer, + "Expected to know revealer" + ); const secret = openAs( entry.value.revelation, @@ -519,7 +522,8 @@ export function getAgentCoValueHeader(agent: Agent): CoValueHeader { initialRecipientID: agent.recipientID, }, meta: null, - publicNickname: "agent" + agent.publicNickname?.slice(0, 1).toUpperCase() + agent.publicNickname?.slice(1), + publicNickname: + "agent" + (agent.publicNickname ? `-${agent.publicNickname}` : ""), }; } @@ -533,7 +537,9 @@ export type AgentCredential = { publicNickname?: string; }; -export function newRandomAgentCredential(publicNickname: string): AgentCredential { +export function newRandomAgentCredential( + publicNickname: string +): AgentCredential { const signatorySecret = newRandomSignatory(); const recipientSecret = newRandomRecipient(); return { signatorySecret, recipientSecret, publicNickname }; diff --git a/src/node.ts b/src/node.ts index 729171ce5..9e53e817f 100644 --- a/src/node.ts +++ b/src/node.ts @@ -11,6 +11,7 @@ import { getAgentCoValueHeader, CoValueHeader, agentIDfromSessionID, + newRandomAgentCredential, } from "./coValue"; import { Team, expectTeamContent } from "./permissions"; import { SyncManager } from "./sync"; @@ -20,7 +21,6 @@ export class LocalNode { agentCredential: AgentCredential; agentID: AgentID; ownSessionID: SessionID; - knownAgents: { [key: AgentID]: Agent } = {}; sync = new SyncManager(this); constructor(agentCredential: AgentCredential, ownSessionID: SessionID) { @@ -28,7 +28,6 @@ export class LocalNode { const agent = getAgent(agentCredential); const agentID = getAgentID(agent); this.agentID = agentID; - this.knownAgents[agentID] = agent; this.ownSessionID = ownSessionID; const agentCoValue = new CoValue(getAgentCoValueHeader(agent), this); @@ -79,9 +78,33 @@ export class LocalNode { return entry.coValue; } - addKnownAgent(agent: Agent) { - const agentID = getAgentID(agent); - this.knownAgents[agentID] = agent; + createAgent(publicNickname: string): AgentCredential { + const agentCredential = newRandomAgentCredential(publicNickname); + + this.createCoValue(getAgentCoValueHeader(getAgent(agentCredential))); + + return agentCredential; + } + + expectAgentLoaded(id: AgentID, expectation?: string): Agent { + const coValue = this.expectCoValueLoaded( + id, + expectation + ); + + if (coValue.header.type !== "comap" || coValue.header.ruleset.type !== "agent") { + throw new Error( + `${ + expectation ? expectation + ": " : "" + }CoValue ${id} is not an agent` + ); + } + + return { + recipientID: coValue.header.ruleset.initialRecipientID, + signatoryID: coValue.header.ruleset.initialSignatoryID, + publicNickname: coValue.header.publicNickname?.replace("agent-", ""), + } } createTeam(): Team { @@ -143,11 +166,6 @@ export class LocalNode { .filter((x): x is Exclude => !!x) ); - newNode.knownAgents = { - ...this.knownAgents, - [agentIDfromSessionID(ownSessionID)]: getAgent(agentCredential), - }; - return newNode; } } diff --git a/src/permissions.test.ts b/src/permissions.test.ts index 24742d980..d5cf4f080 100644 --- a/src/permissions.test.ts +++ b/src/permissions.test.ts @@ -15,9 +15,9 @@ import { } from "./crypto"; function teamWithTwoAdmins() { - const { team, admin, adminID } = newTeam(); + const { team, admin, adminID, node } = newTeam(); - const otherAdmin = newRandomAgentCredential("otherAdmin"); + const otherAdmin = node.createAgent("otherAdmin"); const otherAdminID = getAgentID(getAgent(otherAdmin)); let content = expectTeamContent(team.getCurrentContent()); @@ -34,7 +34,7 @@ function teamWithTwoAdmins() { } expect(content.get(otherAdminID)).toEqual("admin"); - return { team, admin, adminID, otherAdmin, otherAdminID }; + return { team, admin, adminID, otherAdmin, otherAdminID, node }; } function newTeam() { @@ -47,7 +47,7 @@ function newTeam() { type: "comap", ruleset: { type: "team", initialAdmin: adminID }, meta: null, - publicNickname: "team" + publicNickname: "team", }); const teamContent = expectTeamContent(team.getCurrentContent()); @@ -78,11 +78,9 @@ function newTeamHighLevel() { function teamWithTwoAdminsHighLevel() { const { admin, adminID, node, team } = newTeamHighLevel(); - const otherAdmin = newRandomAgentCredential("otherAdmin"); + const otherAdmin = node.createAgent("otherAdmin"); const otherAdminID = getAgentID(getAgent(otherAdmin)); - node.addKnownAgent(getAgent(otherAdmin)); - team.addMember(otherAdminID, "admin"); return { admin, adminID, node, team, otherAdmin, otherAdminID }; @@ -93,7 +91,7 @@ test("Initial admin can add another admin to a team (high level)", () => { }); test("Added admin can add a third admin to a team", () => { - const { team, otherAdmin, otherAdminID } = teamWithTwoAdmins(); + const { team, otherAdmin, otherAdminID, node } = teamWithTwoAdmins(); const teamAsOtherAdmin = team.testWithDifferentCredentials( otherAdmin, @@ -104,7 +102,7 @@ test("Added admin can add a third admin to a team", () => { expect(otherContent.get(otherAdminID)).toEqual("admin"); - const thirdAdmin = newRandomAgentCredential("admin"); + const thirdAdmin = node.createAgent("thirdAdmin"); const thirdAdminID = getAgentID(getAgent(thirdAdmin)); otherContent.edit((editable) => { @@ -126,11 +124,9 @@ test("Added adming can add a third admin to a team (high level)", () => { newRandomSessionID(otherAdminID) ); - const thirdAdmin = newRandomAgentCredential("admin"); + const thirdAdmin = node.createAgent("thirdAdmin"); const thirdAdminID = getAgentID(getAgent(thirdAdmin)); - node.addKnownAgent(getAgent(thirdAdmin)); - teamAsOtherAdmin.addMember(thirdAdminID, "admin"); expect(teamAsOtherAdmin.teamMap.get(thirdAdminID)).toEqual("admin"); @@ -187,8 +183,8 @@ test("Admins can't demote other admins in a team (high level)", () => { }); test("Admins an add writers to a team, who can't add admins, writers, or readers", () => { - const { team } = newTeam(); - const writer = newRandomAgentCredential("writer"); + const { team, node } = newTeam(); + const writer = node.createAgent("writer"); const writerID = getAgentID(getAgent(writer)); let teamContent = expectTeamContent(team.getCurrentContent()); @@ -212,7 +208,7 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers expect(teamContentAsWriter.get(writerID)).toEqual("writer"); - const otherAgent = newRandomAgentCredential("otherAgent"); + const otherAgent = node.createAgent("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); teamContentAsWriter.edit((editable) => { @@ -234,11 +230,9 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers test("Admins an add writers to a team, who can't add admins, writers, or readers (high level)", () => { const { team, node } = newTeamHighLevel(); - const writer = newRandomAgentCredential("writer"); + const writer = node.createAgent("writer"); const writerID = getAgentID(getAgent(writer)); - node.addKnownAgent(getAgent(writer)); - team.addMember(writerID, "writer"); expect(team.teamMap.get(writerID)).toEqual("writer"); @@ -249,11 +243,9 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers expect(teamAsWriter.teamMap.get(writerID)).toEqual("writer"); - const otherAgent = newRandomAgentCredential("otherAgent"); + const otherAgent = node.createAgent("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); - node.addKnownAgent(getAgent(otherAgent)); - expect(() => teamAsWriter.addMember(otherAgentID, "admin")).toThrow( "Failed to set role" ); @@ -268,8 +260,8 @@ test("Admins an add writers to a team, who can't add admins, writers, or readers }); test("Admins can add readers to a team, who can't add admins, writers, or readers", () => { - const { team } = newTeam(); - const reader = newRandomAgentCredential("reader"); + const { team, node } = newTeam(); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); let teamContent = expectTeamContent(team.getCurrentContent()); @@ -293,7 +285,7 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader expect(teamContentAsReader.get(readerID)).toEqual("reader"); - const otherAgent = newRandomAgentCredential("otherAgent"); + const otherAgent = node.createAgent("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); teamContentAsReader.edit((editable) => { @@ -315,11 +307,9 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader test("Admins can add readers to a team, who can't add admins, writers, or readers (high level)", () => { const { team, node } = newTeamHighLevel(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); - node.addKnownAgent(getAgent(reader)); - team.addMember(readerID, "reader"); expect(team.teamMap.get(readerID)).toEqual("reader"); @@ -330,11 +320,9 @@ test("Admins can add readers to a team, who can't add admins, writers, or reader expect(teamAsReader.teamMap.get(readerID)).toEqual("reader"); - const otherAgent = newRandomAgentCredential("otherAgent"); + const otherAgent = node.createAgent("otherAgent"); const otherAgentID = getAgentID(getAgent(otherAgent)); - node.addKnownAgent(getAgent(otherAgent)); - expect(() => teamAsReader.addMember(otherAgentID, "admin")).toThrow( "Failed to set role" ); @@ -355,7 +343,7 @@ test("Admins can write to an object that is owned by their team", () => { type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); let childContent = expectMap(childObject.getCurrentContent()); @@ -386,7 +374,7 @@ test("Admins can write to an object that is owned by their team (high level)", ( test("Writers can write to an object that is owned by their team", () => { const { node, team } = newTeam(); - const writer = newRandomAgentCredential("writer"); + const writer = node.createAgent("writer"); const writerID = getAgentID(getAgent(writer)); expectTeamContent(team.getCurrentContent()).edit((editable) => { @@ -398,7 +386,7 @@ test("Writers can write to an object that is owned by their team", () => { type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); const childObjectAsWriter = childObject.testWithDifferentCredentials( @@ -423,11 +411,9 @@ test("Writers can write to an object that is owned by their team", () => { test("Writers can write to an object that is owned by their team (high level)", () => { const { node, team } = newTeamHighLevel(); - const writer = newRandomAgentCredential("writer"); + const writer = node.createAgent("writer"); const writerID = getAgentID(getAgent(writer)); - node.addKnownAgent(getAgent(writer)); - team.addMember(writerID, "writer"); const childObject = team.createMap(); @@ -449,7 +435,7 @@ test("Writers can write to an object that is owned by their team (high level)", test("Readers can not write to an object that is owned by their team", () => { const { node, team } = newTeam(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); expectTeamContent(team.getCurrentContent()).edit((editable) => { @@ -461,7 +447,7 @@ test("Readers can not write to an object that is owned by their team", () => { type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); const childObjectAsReader = childObject.testWithDifferentCredentials( @@ -486,11 +472,9 @@ test("Readers can not write to an object that is owned by their team", () => { test("Readers can not write to an object that is owned by their team (high level)", () => { const { node, team } = newTeamHighLevel(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); - node.addKnownAgent(getAgent(reader)); - team.addMember(readerID, "reader"); const childObject = team.createMap(); @@ -537,7 +521,7 @@ test("Admins can set team read key and then use it to create and read private tr type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); let childContent = expectMap(childObject.getCurrentContent()); @@ -567,7 +551,7 @@ test("Admins can set team read key and then use it to create and read private tr test("Admins can set team read key and then writers can use it to create and read private transactions in owned objects", () => { const { node, team, admin } = newTeam(); - const writer = newRandomAgentCredential("writer"); + const writer = node.createAgent("writer"); const writerID = getAgentID(getAgent(writer)); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); @@ -596,7 +580,7 @@ test("Admins can set team read key and then writers can use it to create and rea type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); const childObjectAsWriter = childObject.testWithDifferentCredentials( @@ -623,11 +607,9 @@ test("Admins can set team read key and then writers can use it to create and rea test("Admins can set team read key and then writers can use it to create and read private transactions in owned objects (high level)", () => { const { node, team, admin } = newTeamHighLevel(); - const writer = newRandomAgentCredential("writer"); + const writer = node.createAgent("writer"); const writerID = getAgentID(getAgent(writer)); - node.addKnownAgent(getAgent(writer)); - team.addMember(writerID, "writer"); const childObject = team.createMap(); @@ -649,7 +631,7 @@ test("Admins can set team read key and then writers can use it to create and rea test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read", () => { const { node, team, admin } = newTeam(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); @@ -678,7 +660,7 @@ test("Admins can set team read key and then use it to create private transaction type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); expectMap(childObject.getCurrentContent()).edit((editable) => { @@ -703,11 +685,9 @@ test("Admins can set team read key and then use it to create private transaction test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read (high level)", () => { const { node, team, admin } = newTeamHighLevel(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); - node.addKnownAgent(getAgent(reader)); - team.addMember(readerID, "reader"); let childObject = team.createMap(); @@ -717,21 +697,21 @@ test("Admins can set team read key and then use it to create private transaction expect(editable.get("foo")).toEqual("bar"); }); - const childContentAsReader = expectMap(childObject.coValue.testWithDifferentCredentials( - reader, - newRandomSessionID(readerID) - ).getCurrentContent()); + const childContentAsReader = expectMap( + childObject.coValue + .testWithDifferentCredentials(reader, newRandomSessionID(readerID)) + .getCurrentContent() + ); expect(childContentAsReader.get("foo")).toEqual("bar"); }); - test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read, even with a separate later revelation for the same read key", () => { const { node, team, admin } = newTeam(); - const reader1 = newRandomAgentCredential("reader1"); + const reader1 = node.createAgent("reader1"); const reader1ID = getAgentID(getAgent(reader1)); - const reader2 = newRandomAgentCredential("reader2"); + const reader2 = node.createAgent("reader2"); const reader2ID = getAgentID(getAgent(reader2)); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); @@ -779,7 +759,7 @@ test("Admins can set team read key and then use it to create private transaction type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); expectMap(childObject.getCurrentContent()).edit((editable) => { @@ -817,14 +797,11 @@ test("Admins can set team read key and then use it to create private transaction test("Admins can set team read key and then use it to create private transactions in owned objects, which readers can read, even with a separate later revelation for the same read key (high level)", () => { const { node, team, admin } = newTeamHighLevel(); - const reader1 = newRandomAgentCredential("reader1"); + const reader1 = node.createAgent("reader1"); const reader1ID = getAgentID(getAgent(reader1)); - const reader2 = newRandomAgentCredential("reader2"); + const reader2 = node.createAgent("reader2"); const reader2ID = getAgentID(getAgent(reader2)); - node.addKnownAgent(getAgent(reader1)); - node.addKnownAgent(getAgent(reader2)); - team.addMember(reader1ID, "reader"); let childObject = team.createMap(); @@ -834,24 +811,31 @@ test("Admins can set team read key and then use it to create private transaction expect(editable.get("foo")).toEqual("bar"); }); - const childContentAsReader1 = expectMap(childObject.coValue.testWithDifferentCredentials( - reader1, - newRandomSessionID(reader1ID) - ).getCurrentContent()); + const childContentAsReader1 = expectMap( + childObject.coValue + .testWithDifferentCredentials( + reader1, + newRandomSessionID(reader1ID) + ) + .getCurrentContent() + ); expect(childContentAsReader1.get("foo")).toEqual("bar"); team.addMember(reader2ID, "reader"); - const childContentAsReader2 = expectMap(childObject.coValue.testWithDifferentCredentials( - reader2, - newRandomSessionID(reader2ID) - ).getCurrentContent()); + const childContentAsReader2 = expectMap( + childObject.coValue + .testWithDifferentCredentials( + reader2, + newRandomSessionID(reader2ID) + ) + .getCurrentContent() + ); expect(childContentAsReader2.get("foo")).toEqual("bar"); }); - test("Admins can set team read key, make a private transaction in an owned object, rotate the read key, make another private transaction, and both can be read by the admin", () => { const { node, team, admin, adminID } = newTeam(); @@ -880,7 +864,7 @@ test("Admins can set team read key, make a private transaction in an owned objec type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); let childContent = expectMap(childObject.getCurrentContent()); @@ -960,7 +944,7 @@ test("Admins can set team read key, make a private transaction in an owned objec type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); const teamContent = expectTeamContent(team.getCurrentContent()); @@ -994,7 +978,7 @@ test("Admins can set team read key, make a private transaction in an owned objec childContent = expectMap(childObject.getCurrentContent()); expect(childContent.get("foo")).toEqual("bar"); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); const { secret: readKey2, id: readKeyID2 } = newRandomKeySecret(); @@ -1074,11 +1058,9 @@ test("Admins can set team read key, make a private transaction in an owned objec expect(childObject.coValue.getCurrentReadKey()).not.toEqual(firstReadKey); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); - node.addKnownAgent(getAgent(reader)); - team.addMember(readerID, "reader"); childObject = childObject.edit((editable) => { @@ -1086,15 +1068,15 @@ test("Admins can set team read key, make a private transaction in an owned objec expect(editable.get("foo2")).toEqual("bar2"); }); - const childContentAsReader = expectMap(childObject.coValue.testWithDifferentCredentials( - reader, - newRandomSessionID(readerID) - ).getCurrentContent()); + const childContentAsReader = expectMap( + childObject.coValue + .testWithDifferentCredentials(reader, newRandomSessionID(readerID)) + .getCurrentContent() + ); expect(childContentAsReader.get("foo")).toEqual("bar"); expect(childContentAsReader.get("foo2")).toEqual("bar2"); -}) - +}); test("Admins can set team read rey, make a private transaction in an owned object, rotate the read key, add two readers, rotate the read key again to kick out one reader, make another private transaction in the owned object, and only the remaining reader can read both transactions", () => { const { node, team, admin, adminID } = newTeam(); @@ -1103,14 +1085,14 @@ test("Admins can set team read rey, make a private transaction in an owned objec type: "comap", ruleset: { type: "ownedByTeam", team: team.id }, meta: null, - publicNickname: "childObject" + publicNickname: "childObject", }); const teamContent = expectTeamContent(team.getCurrentContent()); const { secret: readKey, id: readKeyID } = newRandomKeySecret(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); - const reader2 = newRandomAgentCredential("reader2"); + const reader2 = node.createAgent("reader2"); const reader2ID = getAgentID(getAgent(reader)); teamContent.edit((editable) => { @@ -1228,7 +1210,6 @@ test("Admins can set team read rey, make a private transaction in an owned objec let childObject = team.createMap(); - childObject = childObject.edit((editable) => { editable.set("foo", "bar", "private"); expect(editable.get("foo")).toEqual("bar"); @@ -1240,14 +1221,11 @@ test("Admins can set team read rey, make a private transaction in an owned objec const secondReadKey = childObject.coValue.getCurrentReadKey(); - const reader = newRandomAgentCredential("reader"); + const reader = node.createAgent("reader"); const readerID = getAgentID(getAgent(reader)); - const reader2 = newRandomAgentCredential("reader2"); + const reader2 = node.createAgent("reader2"); const reader2ID = getAgentID(getAgent(reader2)); - node.addKnownAgent(getAgent(reader)); - node.addKnownAgent(getAgent(reader2)); - team.addMember(readerID, "reader"); team.addMember(reader2ID, "reader"); @@ -1268,17 +1246,22 @@ test("Admins can set team read rey, make a private transaction in an owned objec expect(editable.get("foo3")).toEqual("bar3"); }); - const childContentAsReader2 = expectMap(childObject.coValue.testWithDifferentCredentials( - reader2, - newRandomSessionID(reader2ID) - ).getCurrentContent()); + const childContentAsReader2 = expectMap( + childObject.coValue + .testWithDifferentCredentials( + reader2, + newRandomSessionID(reader2ID) + ) + .getCurrentContent() + ); expect(childContentAsReader2.get("foo")).toEqual("bar"); expect(childContentAsReader2.get("foo2")).toEqual("bar2"); expect(childContentAsReader2.get("foo3")).toEqual("bar3"); - expect(() => childObject.coValue.testWithDifferentCredentials( - reader, - newRandomSessionID(readerID) - ).getCurrentContent()).toThrow(/readKey (.+?) not revealed for (.+?)/); + expect(() => + childObject.coValue + .testWithDifferentCredentials(reader, newRandomSessionID(readerID)) + .getCurrentContent() + ).toThrow(/readKey (.+?) not revealed for (.+?)/); }); diff --git a/src/permissions.ts b/src/permissions.ts index 214e11942..21084ce2a 100644 --- a/src/permissions.ts +++ b/src/permissions.ts @@ -238,7 +238,7 @@ export class Team { addMember(agentID: AgentID, role: Role) { this.teamMap = this.teamMap.edit((map) => { - const agent = this.node.knownAgents[agentID]; + const agent = this.node.expectAgentLoaded(agentID, "Expected to know agent to add them to team"); if (!agent) { throw new Error("Unknown agent " + agentID); @@ -291,7 +291,7 @@ export class Team { new Set( currentlyPermittedReaders.map( (reader) => { - const readerAgent = this.node.knownAgents[reader]; + const readerAgent = this.node.expectAgentLoaded(reader, "Expected to know currently permitted reader"); if (!readerAgent) { throw new Error("Unknown agent " + reader); } From 2cac2e8d4fcc43f0a41eb16b2a4c6c35c574ac65 Mon Sep 17 00:00:00 2001 From: Anselm Date: Mon, 7 Aug 2023 19:54:07 +0100 Subject: [PATCH 4/4] Deal better with private transactions we don't have a key for --- src/coValue.ts | 67 ++++++++++++++++++++++++----------------- src/permissions.test.ts | 24 ++++++++------- src/permissions.ts | 15 ++++++++- src/sync.test.ts | 3 +- 4 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/coValue.ts b/src/coValue.ts index bc3c70b18..ed820a20c 100644 --- a/src/coValue.ts +++ b/src/coValue.ts @@ -231,6 +231,10 @@ export class CoValue { if (privacy === "private") { const { secret: keySecret, id: keyID } = this.getCurrentReadKey(); + if (!keySecret) { + throw new Error("Can't make transaction without read key secret"); + } + transaction = { privacy: "private", madeAt, @@ -298,26 +302,40 @@ export class CoValue { const allTransactions: DecryptedTransaction[] = validTransactions.map( ({ txID, tx }) => { - return { - txID, - madeAt: tx.madeAt, - changes: - tx.privacy === "private" - ? decryptForTransaction( - tx.encryptedChanges, - this.getReadKey(tx.keyUsed), - { - in: this.id, - tx: txID, - } - ) || - (() => { - throw new Error("Couldn't decrypt changes"); - })() - : tx.changes, - }; + if (tx.privacy === "trusting") { + return { + txID, + madeAt: tx.madeAt, + changes: tx.changes, + }; + } else { + const readKey = this.getReadKey(tx.keyUsed); + + if (!readKey) { + return undefined; + } else { + const decrytedChanges = decryptForTransaction( + tx.encryptedChanges, + readKey, + { + in: this.id, + tx: txID, + } + ); + + if (!decrytedChanges) { + console.error("Failed to decrypt transaction despite having key"); + return undefined; + } + return { + txID, + madeAt: tx.madeAt, + changes: decrytedChanges, + }; + } + } } - ); + ).filter((x): x is Exclude => !!x); allTransactions.sort( (a, b) => a.madeAt - b.madeAt || @@ -328,7 +346,7 @@ export class CoValue { return allTransactions; } - getCurrentReadKey(): { secret: KeySecret; id: KeyID } { + getCurrentReadKey(): { secret: KeySecret | undefined; id: KeyID } { if (this.header.ruleset.type === "team") { const content = expectTeamContent(this.getCurrentContent()); @@ -355,7 +373,7 @@ export class CoValue { } } - getReadKey(keyID: KeyID): KeySecret { + getReadKey(keyID: KeyID): KeySecret | undefined { if (this.header.ruleset.type === "team") { const content = expectTeamContent(this.getCurrentContent()); @@ -416,12 +434,7 @@ export class CoValue { } } - throw new Error( - "readKey " + - keyID + - " not revealed for " + - getAgentID(getAgent(this.node.agentCredential)) - ); + return undefined; } else if (this.header.ruleset.type === "ownedByTeam") { return this.node .expectCoValueLoaded(this.header.ruleset.team) diff --git a/src/permissions.test.ts b/src/permissions.test.ts index d5cf4f080..c52a260a9 100644 --- a/src/permissions.test.ts +++ b/src/permissions.test.ts @@ -1194,15 +1194,12 @@ test("Admins can set team read rey, make a private transaction in an owned objec newRandomSessionID(reader2ID) ); - expect(() => expectMap(childObjectAsReader.getCurrentContent())).toThrow( - /readKey (.+?) not revealed for (.+?)/ - ); + expect( + expectMap(childObjectAsReader.getCurrentContent()).get("foo2") + ).toBeUndefined(); expect( expectMap(childObjectAsReader2.getCurrentContent()).get("foo2") ).toEqual("bar2"); - expect(() => { - childObjectAsReader.getCurrentContent(); - }).toThrow(); }); test("Admins can set team read rey, make a private transaction in an owned object, rotate the read key, add two readers, rotate the read key again to kick out one reader, make another private transaction in the owned object, and only the remaining reader can read both transactions (high level)", () => { @@ -1259,9 +1256,14 @@ test("Admins can set team read rey, make a private transaction in an owned objec expect(childContentAsReader2.get("foo2")).toEqual("bar2"); expect(childContentAsReader2.get("foo3")).toEqual("bar3"); - expect(() => - childObject.coValue - .testWithDifferentCredentials(reader, newRandomSessionID(readerID)) - .getCurrentContent() - ).toThrow(/readKey (.+?) not revealed for (.+?)/); + expect( + expectMap( + childObject.coValue + .testWithDifferentCredentials( + reader, + newRandomSessionID(readerID) + ) + .getCurrentContent() + ).get("foo3") + ).toBeUndefined(); }); diff --git a/src/permissions.ts b/src/permissions.ts index 21084ce2a..3f15883c2 100644 --- a/src/permissions.ts +++ b/src/permissions.ts @@ -251,6 +251,10 @@ export class Team { const currentReadKey = this.teamMap.coValue.getCurrentReadKey(); + if (!currentReadKey.secret) { + throw new Error("Can't add member without read key secret"); + } + const revelation = seal( currentReadKey.secret, this.teamMap.coValue.node.agentCredential.recipientSecret, @@ -281,7 +285,16 @@ export class Team { } }) as AgentID[]; - const currentReadKey = this.teamMap.coValue.getCurrentReadKey(); + const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey(); + + if (!maybeCurrentReadKey.secret) { + throw new Error("Can't rotate read key secret we don't have access to"); + } + + const currentReadKey = { + id: maybeCurrentReadKey.id, + secret: maybeCurrentReadKey.secret, + }; const newReadKey = newRandomKeySecret(); diff --git a/src/sync.test.ts b/src/sync.test.ts index 248947761..20c14e788 100644 --- a/src/sync.test.ts +++ b/src/sync.test.ts @@ -909,7 +909,6 @@ test("Can sync a coValue through a server to another client", async () => { const server = new LocalNode(serverUser, newRandomSessionID(serverUserID)); const [serverAsPeer, client1AsPeer] = connectedPeers("server", "client1", { - trace: true, peer1role: "server", peer2role: "client", }); @@ -922,7 +921,7 @@ test("Can sync a coValue through a server to another client", async () => { const [serverAsOtherPeer, client2AsPeer] = connectedPeers( "server", "client2", - { trace: true, peer1role: "server", peer2role: "client" } + { peer1role: "server", peer2role: "client" } ); client2.sync.addPeer(serverAsOtherPeer);