-
-
Notifications
You must be signed in to change notification settings - Fork 149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
O.Path improvements (distribution, arrays) #64
Comments
Thanks for your time and attention on this, @mcpower. I feel like I'm actually pretty close to what I'm trying to achieve (for new readers: drilling into GraphQL responses, which can have // Flatten arrays
type F<T> = T extends (infer U)[] ? (U extends object ? U : T) : T;
// Pick helper
type _Pick<
O extends object,
Path extends Tuple.Tuple<Any.Index>,
I extends Iteration.Iteration = Iteration.IterationOf<"0">
> = O extends (infer A)[]
? A extends object
? _Pick<A, Path, I>[]
: O
: Object.Pick<O, Path[Iteration.Pos<I>]> extends infer Picked
? {
[K in keyof Picked]: Picked[K] extends infer Prop
? Prop extends object
? Iteration.Pos<I> extends Tuple.LastIndex<Path>
? Prop
: _Pick<F<Prop>, Path, Iteration.Next<I>>
: Prop
: never;
}
: never;
// Pick
export type Pick<O extends object, Path extends Tuple.Tuple> = _Pick<O, Path>;
// Pick + Object.At
type Query<TQuery extends object, TPath extends Tuple.Tuple> = Object.Path<
Pick<TQuery, TPath>,
TPath
>; Usage exampleGraphQL: query OrganizationWorkspaces($slug: String!) {
organization(slug: $slug) {
id
workspaces {
edges {
node {
id
name
}
}
}
}
}
GraphQL Code Generator generated query type: export type OrganizationWorkspacesQuery = (
{ __typename?: 'Query' }
& { organization: Maybe<(
{ __typename?: 'Organization' }
& Pick<Organization, 'id'>
& { workspaces: (
{ __typename?: 'WorkspaceConnection' }
& { edges: Maybe<Array<(
{ __typename?: 'WorkspaceEdge' }
& { node: (
{ __typename?: 'Workspace' }
& Pick<Workspace, 'id' | 'name'>
) }
)>> }
) }
)> }
); Getting the 'inner' type Workspace = Query<
OrganizationWorkspacesQuery,
["organization", "workspaces", "edges", "node"]> Intellisense: TODOS
The above may be a limited use-case for GraphQL types, although I think it might also be useful as a general 'lens' into arbitrary levels of an object. This implementation probably needs a bit of refinement. Feel free to tweak and release as Thanks again @mcpower! |
Silly me - I didn't realise I think improving
These two, in combination, should make This is relatively simple to implement, but writing tests / formatting / implementing the boolean flag is a bit tedious. Here's a modification of import {Object, Iteration, Tuple, Any, Union} from 'ts-toolbelt'
type _Query<O, Path extends Tuple.Tuple<Any.Index>, I extends Iteration.Iteration = Iteration.IterationOf<'0'>> = {
0:
O extends object // distribution over O
? O extends (infer A)[] // supporting arrays
? _Query<A, Path, I>
: _Query<Union.NonNullable<Object.At<O, Path[Iteration.Pos<I>]>>, Path, Iteration.Next<I>>
: never
1: O
}[
Iteration.Pos<I> extends Tuple.Length<Path>
? 1
: 0
]
export type Query<O extends object, Path extends Tuple.Tuple<Any.Index>> = _Query<O, Path>
type Maybe<T> = T | null;
type Organization = {id: 1}
type Workspace = {id: 2, name: string}
type OrganizationWorkspacesQuery = (
{ __typename?: 'Query' }
& { organization: Maybe<(
{ __typename?: 'Organization' }
& Pick<Organization, 'id'>
& { workspaces: (
{ __typename?: 'WorkspaceConnection' }
& { edges: Maybe<Array<(
{ __typename?: 'WorkspaceEdge' }
& { node: (
{ __typename?: 'Workspace' }
& Pick<Workspace, 'id' | 'name'>
) }
)>> }
) }
)> }
);
// evaluates to { __typename?: "Workspace" | undefined } & Pick<Workspace, "id" | "name">
type Result = Query<OrganizationWorkspacesQuery, ['organization', 'workspaces', 'edges', 'node']> Interestingly, in your given example P.S. |
Hi all, I'm not into GraphQL, but I believe this should work: import {IterationOf} from '../Iteration/IterationOf'
import {Iteration} from '../Iteration/Iteration'
import {Next} from '../Iteration/Next'
import {Pos} from '../Iteration/Pos'
import {Length} from '../Tuple/Length'
import {At} from './At'
import {Cast} from '../Any/Cast'
import {NonNullable as UNonNullable} from '../Union/NonNullable'
import {Index} from '../Any/Index'
import {Tuple} from '../Tuple/Tuple'
type _PathUp<O, Path extends Tuple<Index>, I extends Iteration = IterationOf<'0'>> = {
0: At<O & {}, Path[Pos<I>]> extends infer OK
? OK extends unknown
? _PathUp<UNonNullable<OK>, Path, Next<I>>
: never
: never
1: O // Use of `NonNullable` otherwise path cannot be followed #`undefined`
}[
Pos<I> extends Length<Path>
? 1 // Stops before going too deep (last key) & check if it has it
: 0 // Continue iterating and go deeper within the object with `At`
]
/** Get in **`O`** the type of nested properties
* @param O to be inspected
* @param Path to be followed
* @returns **`any`**
* @example
* ```ts
* ```
*/
export type PathUp<O extends object, Path extends Tuple<Index>> =
_PathUp<O, Path> extends infer X
? Cast<X, any>
: never
type Nested = {
a?: NestedA | NestedA[] | null | number
z: 'z';
};
type NestedA = {
b?: NestedB | NestedB[] | null | number
y: 'y';
};
type NestedB = {
c: 'c';
z: 'z';
};
// resolves to 'c' | 'z'
type CObject = PathUp<Nested, ['a', 'b', 'c' | 'z']>;
type CArray = PathUp<Nested, ['a', 'b', number, 'c' | 'z']>; |
@leebenson |
PS @mcpower the What do you think? |
@pirix-gh - looks great! I made a slight modification to drop the type _PathUp<
O,
Path extends Tuple.Tuple<Any.Index>,
I extends Iteration.Iteration = Iteration.IterationOf<"0">
> = {
0: Object.At<O & {}, Path[Iteration.Pos<I>]> extends infer OK
? OK extends (infer U)[]
? _PathUp<NonNullable<U>, Path, Iteration.Next<I>>
: OK extends unknown
? _PathUp<NonNullable<OK>, Path, Iteration.Next<I>>
: never
: never;
1: O; // Use of `NonNullable` otherwise path cannot be followed #`undefined`
}[Iteration.Pos<I> extends Tuple.Length<Path>
? 1 // Stops before going too deep (last key) & check if it has it
: 0]; // Continue iterating and go deeper within the object with `At`
/** Get in **`O`** the type of nested properties
* @param O to be inspected
* @param Path to be followed
* @returns **`any`**
* @example
* ```ts
* ```
*/
export type PathUp<
O extends object,
Path extends Tuple.Tuple<Any.Index>
> = _PathUp<O, Path> extends infer X ? Any.Cast<X, any> : never; Thanks so much to you and @mcpower for indulging my specific use-case! Really appreciate your time. |
@leebenson I can't add your modified type to this lib, unfortunately. I need to follow some rules about consitency. And since we are talking about So by acessing The second reason is that I don't see why we should treat But if the type CArray = PathUp<Nested, ['a', 'b', Index, 'c' | 'z']>;
// we now know that we're accessing all indexes of an array/tuple Maybe you could start a lib of your own for graphQL-related type helpers? I'm glad we could help you. I'll probably proceed with publishing the Let me know if there's anything else, before I close the issue :) |
type Tuple = [
[[[1]]],
2,
3
]
type test0 = PathUp<Tuple, [0, 0, 0, 0]>; // 1
type test1 = PathUp<Tuple, [Index]>; // 3 | [[[1]]] | 2 |
👍 totally cool, my version only really applies to this limited use-case. Having the more flexible/general purpose as part of the lib is great. Thanks again! |
🍩 Feature Request
Is your feature request related to a problem?
See @leebenson's comment on #57.
Describe the solution you'd like
Something which can do something like this:
This follows on from the discussion on #57. From the previous discussion:
As an update to that, I believe this feature is impossible to get "perfect", for similar reasons to why getting the nested inner type of an array is impossible. In a type definition, you can't directly refer to the type definition without some sort of indirection - see the TS 3.7 blog post for more details on that.
Here's the code I tried:
TypeScript 3.7 complains that it's a circularly referenced type. Adding a
type Lazy<T> = T
doesn't help either - I think the type system eagerly evaluates all branches of a conditional type at runtime (without expanding nested types like objects and arrays). That means we need to add some level of indirection at every level we traverse.The other way of doing it is manually "unrolling" the recursion many times, like the
ArrayType
example above:If we were to do this in O.P.At, it would result in pretty unreadable code. Instead, we could "wrap" the return value of the unlimited depth O.P.At with something
{ __wrap: T }
(we want this wrapping to be unique so we don't accidentally unwrap a user's type) and "unwrap" it at the end with something likeArrayType
above.In fact, I found a way of getting 2n levels of recursion with n "unrolls" (Playground link) - so we can pretty easily get lots of unrolling with little code:
We nest the double-types inside a conditional to prevent TypeScript from expanding the types to the user... and filling their screen with
ArrayType1
s 😛.Using this, we can write some code that works on TypeScript 3.6:
@pirix-gh What do you think? The
ArrayType
utility type could also come in handy as well, so we may want to add that tots-toolbox
too.The text was updated successfully, but these errors were encountered: