Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Peeja/0a4d408a68f69f533b58ba9d82100076 to your computer and use it in GitHub Desktop.
Save Peeja/0a4d408a68f69f533b58ba9d82100076 to your computer and use it in GitHub Desktop.
Reverse mapped types brain dump

mapped types syntax and intro

https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gRgKygXigbwFBW1AZge3wC4oBnYAJwEsA7AcwBosc4BDCkmgVwFs4IKGAL4YMoSFACSAEwg1gVUAB4AKgD4U6ZtgDaAaSi0oAawgh8uKCoC6JFfuvDR46AGVWPaKhlyFy+AhqGAD0wTgAegD8omLg0AAKFPg8VKRUuCCqGqiYOFD6hjQmZhZWtlAAFACUKBqJyakQqg5BIrES9SlpuFQQ0pqdqemZAUGhEdEYsgDGADbs0NP4NORQFGDTJIPdvdKiSyvAeFzAXBReaxsAdGwU1SFheU-PUFGiuCdnEFfAABZyFQqADdWLMasgNLkcCDZtooOM3kIqkA

a tweet that prompted the idea for the talk

https://twitter.com/kentcdodds/status/1608187990215655424

but since this isn't possible with satisfies (it doesn't participate in inference, in an example like that it only provides contextual types) the answer was to use a function with a reverse mapped type

https://twitter.com/ecyrbedev/status/1608211211425923073

code from the tweet:

https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwGcpsDEBPAeQCMArcDAOSgFsQAeAFQD4AKAKHjwctAFzwA3gMHwA2gGl4WfAGsQZHIngcAumJ6oWIMXICU8ALxd4ANxxZgAbikBfPibG37TvkRLlqdJhMrDySglB6BqxmlhJSglEgTtLwAPSp8AB6APwuADRSVJGGMVZh0onJ0ulZuYLOBc4m3r5YpJS09MEgofAR8DylEg3wRQND4s7wTU41OXwLQA

this example infers a type like { a: unknown; b: unknown; } for T but we can take it further and infer more interesting things instead of uknowns:

https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwAcYcA3LUANSgmRAB4AVAPgAoAoeeHAIwCsAueAG8OneAG0A0vCz4A1iACeORPAYBdQSLFiS1QQynqANKJ1huglqigBbEAaMBKeAF4m8EjnIBuM-ABfUSCnQS9fNjYiUnIQKhoQFm14KC1-PQhBABYAJlNzS3gWDNoXd2EgnQB6KrEAPQB+YPz4QuTODMEAcigAdxAAZxx7AEIuls4LKxKQMo8hSrEa+qbOANMApz8o4jJKaloklK1PfXhc43gpormKwMu208z4Hv6h0fGrwpZbhcDArZsZaNSJsIA

prisma-like extension example for derived computations (compute property) with data requirements (needs property):

https://www.typescriptlang.org/play?ssl=4&ssc=21&pln=4&pc=3#code/CYUwxgNghgTiAEkoGdnwKrJDeBvAUPPAA4CuARhAJZjwB2UAtiAFzzIAuMVdA5oSQrVa0Tm07c+AspRrwovVvVKNy2fAF98+AGak6YDlQD2deCAAeHEHWAAeACoA+ABQCeZDmwJEiAbQBpeB54AGsQAE9jHXgHAF1vAV96EBBgZDYHQLiAbiTfMGNGTyUXCzYABRpQu0xsABowyOjY7PgAMiaomLqYJwBKeABeJ3k6CLzkjUn4LUGfeDgOUhgzOlIICABCPK18QrpOeAth8ysbYBcFnSYqCAiAOSYlBaI6VPTveFEveC5SBAaer5QrFUjWNguUhYGCDEbwaHYAB0P2ByQA9OjkskAHoAfgEQM0-Ty+1MRwip0s1lsVwEek2T2YiWS7zSGTw+TezzY-xAaOxP15MAB+SJyVBJUhiNhw1GAAMACS4GVIhjMDTwZWqn4aeUCoiY7G+fGE4EaEn4IA

the interesting bit here is also that the intersection acts as a "filter": keyof T[K] & keyof User. By intersecting two unions of strings we end up with members that are available in both unions

bindAll example, inferring a tuple using the same reverse mapped type technique, the variadic tuple type at the argument position hints TS to infer this a tuple (listeners: [...Bindings<Types>]):

https://www.typescriptlang.org/play?#code/C4TwDgpgBAQglgOwCaIOYGcA8AVKEAewEy6UAShAIZID2CANiAIIBOLlIm6wLaAfHygBeKAG8AUFCgBtANJREUANYQQNAGZRsAXQBcYyVKihI+7HO0BuQ1PpxuxCC30AKCADczFgJTDB7mjgkaykAX2tw8XEkCABjekoWaHUAVwRY4Dg6KAAjRCQmenpMQ1wCIhIoAAlsAFkAGQBReggAW2JgABpS8AhScuIkUgpqOkZWdk4ABRp0dDgclsb3DuxezBU1TWwBcT4XYETUCGAzTqg7BwQndH1pADpH+GQ0LDXIdD5tb30AoOtxCZoDM5gslisEMB3hBMLJBCJ5ANKgADOgAElEiHUTi0vVCyKgAH5cZAoPprisWAC8shCvQXKJQlBKKQag0AJIIMApYDNNodc7SQwSIzGXr6ABEixSLAl3VFlwqTlcHl8QkEItFeHcNiMAHo9VAAHqE3WheVQc3C3VAyXxOCxJRy3WKxzOKBudxqjW6qQeX1QA3G02iq1hbrfaxAA

a more robust version of the bindAll that actually maps the inferred string types to their event types ('click' -> MouseEvent), with nice autocompletes for those string types:

https://www.typescriptlang.org/play?ts=5.0.0-dev.20230202#code/C4TwDgpgBAqgdgIwJZwCYDE5QLxQBQCUOAfFAG4D2SqA3AFCiSxwDWcFA7nOgK5wDGwJBSy48AOkkBDAE4BzAM4AuKFLggA2gF0i2UmpD1G0AJJwAZhBkBRMhDjAAKuAgAeR49lyIwUrg9ePlAQAB7A9qgKUADedFCqqKi29sAAMkgK4XBWeMYqKJYyUAAKADRQkuJeyqrqBCqU1PTxUonJDumZ9jl5UAVWJQBM5ZXVKgb15FS0dAC+cVAA-CULKtl2MkYuUGaF7cCuCwHyPqVHzkyhWZFQmTIocnR+OxZW+xdux96+wWERUR4XAtlvtVlAAAYiAAk0UBkFm4N+1yiLAgIAo5igXx8wJKsikAFsfFYFK5rGEZFJBO5PCdgBpIXAYXCIAitOV4GxONw+IJhHBiMQNAAGLRg-ZbJidLJWADyCAAVhBqY59kj-lB9s9YvEABZqVAAGwg+1yuoyKml3Rk8qVKq15QgdgcKlVzuAk0aM1mkugVuyMhpgWA6rQUXewfKbpSoZuWpwCwAPlB-XLFcqDtGHMQkzEFvF4maLVjad9HWRXftPdNmgWfQxtgAhFCoB6HeLYkNXDURuk4TXu0unc7bbth27Ae5wOT9u4PJ79nVQXosmhQBaGjIymSWrfWoN08q7N7ug9lksuQW1ihgIQiBSLFQICgUY1qKDJgCCbXdqZtt-5BR6HrBsmGbNAHlJc5g1jcNB0jEdICiMcbgAJQgVoRENEBPxkSkQFcOdpxzbUFg0ABpPosFRdFMRZBQtBUJd4hXD4FAorRa3iTcugDXdeKsM9ThePZT07KM2I4qAADIJynOQr3zKAbzvOAHyfF83ywL8fxSP9ZQA+9a3rEDUGVQ1ZGgcxeVUqBkDQT9DUNdsSxglC4JSIdgDODs2NgqB0MwuBsNw-CXMLY8bEHFwhJ+WSiLkIhkzwBKZJiWYCAWEi8AWYBg1dLyfLsltIJUDRKnA1tp1JcSLyQ4h2QWBR9RkCBUAM1T1LszSMO0qBvySX89wDDrALoSZ4HsjA4HoOgpscw08GiWZVCiAAJRwAFlUkbHhgGAERrGNIkHHKDQFmY5cXBUAAifhN34FgbqK7jhqsFQ8CdFJdFIZaXqgAB6AGCxB+IAD1FgWWYisu3obpo1BuWepSeO3D6vocH70v+oHQZBiGobOHRmiAA

that code can be found in https://github.com/alexreardon/bind-event-listener , the autocomplete will only work in 5.0 though since it depends on this PR: microsoft/TypeScript#51770

createMachine example: no nesting, just providing autocompletes and types for the transitions targets (play with the strings in NEXT: "") + initial property also depends on the available states. The interesting bit is also that we can use keyof T outside of the mapped type

https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXzDigxAFkowALLVEAHgBUA+ACjD0SwHMAueAbwBQ8eDSzYoEPgGsQATxyJ4DANzD4AZwzEQGvkJEiA2gGlR+WQqUMAuvvWH4eAPx8ASuBwxgdLTBpcADTwlorKTGqOAL6R8DGCUQCUfABuOFjAaoKEIDrkVDQgLAbm4liSfABEUJWB6lo6egIO8FD2jiJ47R0iAHIAogAaDFW1LSJRddFThgBG3Y5dzT19QyPwlWM9ky07jmALhkslPQPDozPTu5fwwIedqPeGZ+ubNxM3e3F1SWpAA

createMachine with recursive structure, transition targets only allow strings from the given level of nesting, the same is true about initial. State<T[K]> & ... allows this to be recursive - T[K] is something that TS just tries to figure out and when it recursively goes into State type alias then it sees a mapped type there again and gathers inferences for it in the same way. The inferred T here becomes: { a: unknown; b: { nested: unknown; anotherNested: unknown; }; }

https://www.typescriptlang.org/play?#code/C4TwDgpgBAysCGwIB4AqA+KBeKBvAUFFAJYB2xwx8ANgPwBcUA1hCAPYBmUqA3IVAGcESAQzz8iAbQDSJUs1aduAXUZxEKVDOWYAZOKKGobUmIBKEAMZsATgBNkQm2QDmAGgXsuGPkYC+vlAB+MH4dlbU8DbQHACupJaUJlCW0RoAsvCWABZkmugAFNakHMQuasL5AJSMvPj4qRAZWbmkEAUERGQUVNSMAET9bvxCGgKMnYbwExKGJjNGRgByAKIAGqgDQ7NEfsP++4YARguL3ZQ0A21CEHbbi8akpw+rG1uHBzuCleMGD1DXJB2Z7-eZ-f6GVArGCbKCDD7-PZfXYIozwUhsYDZCA2JYQG7A8Ggp5EiFQmFXfFA+4QoKowxIh6MhmHJF+Kp8IA

we can go even further with createMachine and the upcoming const type parameters:

https://www.staging-typescript.org/play?ts=5.0.0-pr-51865-14#code/C4TwDgpgBAaghgGwK4QM4B4AqA+KBeKTAbQGsIQB7AM0IF0BuAKEdEigHEJgBlYOYNFlwFMUCAA8BAOwAmqKAG8oqPgNQAuKBQBGAKwgBjYFAC+UAPyEiAIhX8012lE1SIANwgAnJi3DQAkqgAcu5eQvhQRJhOEtJyka4enk6WAIzOUAAMPqzQnMD+clgANBz8ABZeEDJikhCy8iqeAJZSAOYRiV7CUIEhSeGx9fGpjFAWZcCVntVjGdbNMta1cfJklDSYc5b5hRjwyIL5vPYYONil7BVVNQA+UAAGAMQAJArEC0tOAGTKwC3tEwPbBzTS7IoHFAYY6qQTnS7XGYybA5PxQACCEXBGAUc0WmmscGsTHGdjUmlx43G2jgUlpFLmVKg+Kg1kqEHIxMZphJVNpFCmXgZTPGLOsclQMjgkq5TJMvPljBMKOYuSgJwEAGEKFIqM02ug5pgNWgVsN5AAlQwUTwydBNVptUom7W6-U9K0GG12h3tZ2w116g20kDYC5GvZmhp-AEdAi+tqMHqU5kycyaBO81rNYDNRDpqDraiEE3yX6ZuZktAFlPjIgAaWZUkL5GLxthqFomhdOqD6BhpywpYbtAuhD2KvGivGOoLnu99v+jtKmEj9weADo3kXNqWoOWl4DgUqfDJDAg4DMoFQkFIjM0dVADDN7ABZOAGcqtCDoL1SFSEFG8Q9m6BoDmoQiXFwexCGGAAUcx-kGmhbAAlJobgUIszDPhAb4fl+rhwSm2a5ogBJEsUeIyJoADk-KCp4hS0VRpIdsKfIcUypF5ggBJUBQFDWKxIpVhoijckyAkUFxIrcTRUC0QA7hQSkAPpnhAYBqYstGSVSJgiXKRmmCJhlzDqskAKIwHRelTkqqFMEAA

What is mind-bending about this example is that our type parameter depends on itself (!):

const T extends StateConfig<GetStates<T>, GetIds<T>>

We are able to "crawl" T with GetIds, gather those available state IDs and provide that as one of the part of the available transition targets. Those IDs become available "globally" within this machine.

We don't exactly have to rely on const type parameters. We could do this even today by introducing an extra TIds type parameter:

https://www.staging-typescript.org/play?noErrorTruncation=true&ts=5.0.0-dev.20230220#code/C4TwDgpgBAaghgGwK4QM4B4AqA+KBeKTAbQGsIQB7AM0IF0BuAKEdEigHEJgBlYOYNFlwFMUCAA8BAOwAmqKAG8oqPgNQAuKBQBGAKwgBjYFAC+UAPyEiAIhX8012lE1SIANwgAnJi3DQAkqgAcu5eQvhQRJhOEtJyka4enk6WAIzOUAAMPqzQnMD+clgANBz8ABZeEDJikhCy8iqeAJZSAOYRiV7CUIEhSeGx9fGpjFAWZcCVntVjGdbNMta1cfJklDSYc5b5hRjwyIL5vPYYONil7BVVNQA+UAAGAMQAJArEC0tOAGTKwC3tEwPbBzTS7IoHFAYY6qQTnS7XGYybA5PxQACCEXBGAUc0WmmscGsTHGdjUmlx43G2jgUlpFLmVKg+Kg1kqEHIxMZphJVNpFCmXgZTPGLOsclQMjgkq5TJMvPljBMKOYuSgQQo-ikVDCOAiUVoURWw3ktJAE0yGS6yVVaJOAgAwhRtc02ug5ph7Whih69saGn8AR0CE1Wm1GD1KcyZOZNJg9rzWs1gM1ELGoOtqIQvfJfqH2ryyWh01HxkQANLMqQZ8hZz2w1C0TRep0ut0w05YHMV2gXQh7FXjRXjZ3pgBKhgonhk6HzbVKDze8bkQKg9weADo3pnNjmoHn-mGgSDFYwZIYEHAZlAqEgpEZms6oAYZvYALJwAzlVoQEr9+JDAGc7YAAFHMBjOlQrrNrCrZQW6mClMuqAggAlJobgUIszAvhA76ft+rggVGSYpogBJEj6ooyJoADkb4AJq9AAIrRVF-Kcwp8lxTIsrR6JBAA8pgAASACiY4APr+KxpQAPRyVAUzNI05QUEgCA1No0CIAgFAAO7VM4YEilWyapggBJUBQFDWOxQ7sSY7HOjxUBiTAdG0XMJhKqhTBAA

As long as type parameter is constrained to a primitive type (string in this example), TS infers unions of literal types - so we can infer 'a' | 'b' based on all occurences in the argument, instead of just a string.

There are some problems here though

  1. id: 'ANOTHER_ID' isn't allowed in within states. It turns out that, currently, TS doesn't infer to other type parameters within the mapped type... but with this PR it becomes capable of that: microsoft/TypeScript#52737 , we can even verify it with a playground that uses this PR:

https://www.staging-typescript.org/play?ts=5.0.0-pr-52737-7#code/C4TwDgpgBAaghgGwK4QM4B4AqA+KBeKTAbQGsIQB7AM0IF0BuAKEdEigHEJgBlYOYNFlwFMUCAA8BAOwAmqKAG8oqPgNQAuKBQBGAKwgBjYFAC+UAPyEiAIhX8012lE1SIANwgAnJi3DQAkqgAcu5eQvhQRJhOEtJyka4enk6WAIzOUAAMPqzQnMD+clgANBz8ABZeEDJikhCy8iqeAJZSAOYRiV7CUIEhSeGx9fGpjFAWZcCVntVjGdbNMta1cfJklDSYc5b5hRjwyIL5vPYYONil7BVVNQA+UAAGAMQAJArEC0tOAGTKwC3tEwPbBzTS7IoHFAYY6qQTnS7XGYybA5PxQACCEXBGAUc0WmmscGsTHGdjUmlx43G2jgUlpFLmVKg+Kg1kqEHIxMZphJVNpFCmXgZTPGLOsclQMjgkq5TJMvPljBMKOYuSgQQo-ikVDCOAiUVoURWw3ktJAE0yGS6yVVaJOAgAwhRtc02ug5ph7Whih69saGn8AR0CE1Wm1GD1KcyZOZNJg9rzWs1gM1ELGoOtqIQvfJfqH2ryyWh01HxkQANLMqQZ8hZz2w1C0TRep0ut0w05YHMV2gXQh7FXjRXjZ3pgBKhgonhk6HzbVKzze8bkQKg9weADo3pnNjmoHn-mGgSDFYwZIYEHAZlAqEgpEZms6oAYZvYALJwAzlVoQEr9+JDAGc7YAAFHMBjOlQrrNrCrZQW6mClMuqAggAlJobgUIszAvhA76ft+rggVGSYpogBJEj6ooyJoADkb4AJq9AAIrRVF-Kcwp8lxTIsrR6JBAA8pgAASACiY4APr+KxpQAPRyVAtI1FMzTyGpSkIAgFAAO7VAAhNyopSMmqYIASVAUBQ1jsUO7EmOxzo8VAYkwHRTyMSxtFzCYSqoUwQA

  1. there is still a problem here though. As mentioned, TS gathers inferences for those string literals based on all occurences - it doesn't understand which one is more important and which spots are creating our source of truth. In our example we only want to construct that type from all id: 'someId' but we don't want to infer that from on: '#someId'. The latter should be typed based on the former - constrained to it. In other words, we expect an error on EV: '#INVALID':

https://www.staging-typescript.org/play?ts=5.0.0-pr-52737-7&ssl=54&ssc=5&pln=54&pc=19#code/C4TwDgpgBAaghgGwK4QM4B4AqA+KBeKTAbQGsIQB7AM0IF0BuAKEdEigHEJgBlYOYNFlwFMUCAA8BAOwAmqKAG8oqPgNQAuKBQBGAKwgBjYFAC+UAPyEiAIhX8012lE1SIANwgAnJi3DQAkqgAcu5eQvhQRJhOEtJyka4enk6WAIzOUAAMPqzQnMD+clgANBz8ABZeEDJikhCy8iqeAJZSAOYRiV7CUIEhSeGx9fGpjFAWZcCVntVjGdbNMta1cfJklDSYc5b5hRjwyIL5vPYYONil7BVVNQA+UAAGAMQAJArEC0tOAGTKwC3tEwPbBzTS7IoHFAYY6qQTnS7XGYybA5PxQACCEXBGAUc0WmmscGsTHGdjUmlx43G2jgUlpFLmVKg+Kg1kqEHIxMZphJVNpFCmXgZTPGLOsclQMjgkq5TJMvPljBMKOYuSgQQo-ikVDCOAiUVoURWw3ktJAE0yGS6yVVaJOAgAwhRtc02ug5ph7Whih69saGn8AR0CE1Wm1GD1KcyZOZNJg9rzWs1gM1ELGoOtqIQvfJfqH2ryyWh01HxkQANLMqQZ8hZz2w1C0TRep0ut0w05YHMV2gXQh7FXjRXjZ3pgBKhgonhk6HzbVKzze8bkQKg9weADo3pnNjmoHn-mGgSDFYwZIYEHAZlAqEgpEZms6oAYZvYALJwAzlVoQEr9+JDAGc7YAAFHMBjOlQrrNrCrZQW6mClMuqAggAlJobgUIszAvhA76ft+rggVGSYpogBJEj6ooyJoADkb4AJq9AAIrRVF-Kcwp8lxTIsrR6JBAA8pgAASACiY4APr+KxpQAPRyVAtI1FMzTyGpSkIAgFAAO7VAAhNyopSMmqYIASVAUBQ1jsUO7EmOxzo8VAClQAAAsAqAALQSJARg+Z4nhTtyYkwHRTz+EEMDogAMjJtFzCYSqoUwQA

And with a small help of NoInfer magic we can achieve that:

https://www.staging-typescript.org/play?ts=5.0.0-pr-52737-7#code/C4TwDgpgBAaghgGwK4QM4B4AqA+KBeKTAbQGsIQB7AM0IF0BuAKEdEigHEJgBlYOYNFlwFMUCAA8BAOwAmqKAG8oqPgNQAuKBQBGAKwgBjYFAC+UAPyEiAIhX8012lE1SIANwgAnJi3DQAkqgAcu5eQvhQRJhOEtJyka4enk6WAIzOUAAMPqzQnMD+clgANBz8ABZeEDJikhCy8iqeAJZSAOYRiV7CUIEhSeGx9fGpjFAWZcCVntVjGdbNMta1cfJklDSYc5b5hRjwyIL5vPYYONil7BVVNQA+UAAGAMQAJArEC0tOAGTKwC3tEwPbBzTS7IoHFAYY6qQTnS7XGYybA5PxQACCEXBGAUc0WmmscGsTHGdjUmlx43G2jgUlpFLmVKg+Kg1kqEHIxMZphJVNpFCmXgZTPGLOsclQMjgkq5TJMvPljBMKOYuSgQQo-ikVDCOAiUVoURWw3ktJAE0yGS6yVVaJOAgAwhRtc02ug5ph7Whih69saGn8AR0CE1Wm1GD1KcyZOZNJg9rzWs1gM1ELGoOtqIQvfJfqH2ryyWh01HxkQANLMqQZ8hZz2w1C0TRep0ut0w05YHMV2gXQh7FXjRXjZ3pgBKhgonhk6HzbVKzzeGq1Os8WAHQKg9weADo3pnNjmoHn-mGgSDFYwZIYEHAZlAqEgpEZms6oAYZvYALJwAzlVoQCU-bxEMAZztgAAUcwGM6VCus2sKtnBbqYKU8ZyCCACUmhuBQizMB+EDfr+-6uBBUZJimiAEkSPqijImgAORfgAmr0AAijF0X8pzCnyfFMiyjHokEADymAABIAKJjgA+v4nGlAA9EpUC0jUUzNPIWlqQgCAUAA7tUACE3KilIyapggBJUBQFDWNxQ7cSY3HOgJUAqVAAACwCoAAtBIkBGAFnieFO3JSTATFPP4QQwOiAAyCmMXMJhKphTBAA

At the moment it's only possible to infer into T[K], it has to stay "naked". I believe though that this can be improved further and I created a proposal that would allow us to infer into T[K]['data'], T[K]['result'], etc. I call this inferring to "concrete index types":

microsoft/TypeScript#51612

I strongly believe that this new capability would allow some libraries to ditch such monstrosities:

https://github.com/TanStack/query/blob/17816d6eedc7450fd4c6fcdfa2fad87272327c2a/packages/react-query/src/useQueries.ts#L34

for something much simpler, like here (this is just illustrative and doesn't cover for everything that the real thing actually covers at the moment):

https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgIoFdoE8Bi6QJjAD2IAwqZAB5gA8AUMsgCobYDSEWyENEIAEwDOyIWCigA5gBp6APmQBvRsgCOmKFk5YAXCzabt9AL716YLAAcUBrAHlLRUiIC8SlUwDWXPWIkhJD2QBODA4AH49fE8QYgB3EABuIJgQABFQiKiQGPiklVNzKxQAVSEIA2AIIQcnECFaZh4+QREAJQg4AVIAGywAQSgoOCxaW1qSerkFN2UmAG12ZFBkbyxiGBYAXT05pjUNLR8WRa35gHI1862g9WwcEEjkAAogpkIqPVs8AjqKcF4dGYpwuVy2clk+2QAEpkC4FAAFKDEAC2wHKjRB51SGTC1wUAB8Tuwztj0plrkFyj0IIQns8QmE9MCSRccRStrD4cTSYy4JSmKZCgJaT04FAUDB8IRJsh0OVKtUGEwmoD+MJkB0ur0BkMRmNDhNnHJ5M87hJqnp5gA6W1lCoaKo1RyTBrMOScvQAN2IwAEySAA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment