Skip to content

Instantly share code, notes, and snippets.

@Andarist
Created February 23, 2023 08:49
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Andarist/0294519c570a52fb14f4cdd3d589c880 to your computer and use it in GitHub Desktop.
Save Andarist/0294519c570a52fb14f4cdd3d589c880 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 `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