Created
February 23, 2023 08:49
-
-
Save Andarist/0294519c570a52fb14f4cdd3d589c880 to your computer and use it in GitHub Desktop.
Reverse mapped types brain dump
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 `uknown`s: | |
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: https://github.com/microsoft/TypeScript/pull/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 (!): | |
```ts | |
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: https://github.com/microsoft/TypeScript/pull/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 | |
2. 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": | |
https://github.com/microsoft/TypeScript/issues/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