`ts-deep-pick` is a TypeScript type-only utility package that allows for
type-safe, deep picking/omitting of properties from nested object types.
Powered by the
[template literal type released in TypeScript 4.1](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-beta/#template-literal-types),
the type `DeepPick` will offer all combinations of nested pick/omit string
patterns that will then return the deeply picked/omit type according to the
[deep-picking grammar](#Grammar).

![Screenshot](docs/screenshot.png)

## Usage

### `DeepPick`

```ts
import { DeepPick } from "ts-deep-pick";

interface FooBar {
  foo: ABC;
  bar: ABC;
}

interface ABC {
  a: number;
  b: number;
  c: number;
}

// Result type: { foo: { a: number } };
// including a property "positively" omits the exluded properties.
type FooWithJustA = DeepPick<FooBar, "foo.a">; // "." joins nested properties

// Result type: { foo: { a: number, b: number } };
type FooWithJustAB = DeepPick<FooBar, "foo.a" | "foo.b">;properties

// Result type: { foo: { a: number, b: number } };
type FooWithoutC = DeepPick<FooBar, "foo.!c">; // "!" omits a property

// Result type: { bar: ABC };
type Bar = DeepPick<FooBar, "!bar">; // "!" omits a property

// Result type:
// {
//    foo: { a: number, b: number },
//    bar: ABC
// };
// "~" mutates the property's nested membership without explicitly picking
// the property and thereby omitting the remaining.
type FooBarWithoutFooC = DeepPick<FooBar, "~foo.!c">;

// Result type: Array<{ foo: ABC }>
type ArrayWithFoo = DeepPick<FooBar[], "[].foo"> // "[]" accesses an array item.

// Result type: FooBar
type ArrayWithFoo = DeepPick<FooBar, "*"> // "*" is the "identity" operand
```

### `DeepPickPath`

Returns the possible path that can be used to passed into `DeepPick` for a given
structure.

```ts
import { DeepPickPath } from "ts-deep-pick";

// assume FooBar as defined earlier.
// "foo" | "bar" | "!foo" | "!bar" | "~foo.a" | "~foo.!a" | ...
type Path = DeepPickPath<FooBar>;
```

## Behavior

- Union paths are treated like a combination of paths. For example, `"a"|"b"`
  would mean "pick properties `a` and `b`".
- At each level, the "picked set" is determined before omitting and mutating.
  If the paths at the given level include at least one explicitly picked
  key (`"a"`) then these keys and the mutations (`"~a"`) become the picked
  set. In this case, omits are redundant because they are either no-op
  (omitting keys that aren't picked) or they make the picks no-op (omitting
  keys that are picked).
- If the paths at the given level consist only of omits (`"!a"`) and
  mutations (`"~a"`), then the omit operation takes place on the full set of
  keys.

### Examples

- `"a" | "b"` will pick just properties `a` and `b`.
- `"a" | "!a"` will pick nothing (`a` is picked but is then omitted.)
- `"~a.foo"` will pick everything at the outermost level, and pick foo as the
  only property of `a`.
- `"!a" | "~b.foo"` will pick everything except for `a` at the outermost level,
  and modifies `b` such that only `foo` is available there.

## Grammar

The character tokens `.`, `!`, `~`, `[]` and `*` define the property
separator, omit prefix, mutation prefix, array index property and the
pass-through (return the original type) operand respectively. These tokens can
be replaced by defining the following interface and passing it as the extra
type parameter to `DeepPick` and `DeepPickPath`.

```ts
interface DeepPickGrammar {
  array: string;  // default: "[]"
  prop: string;   // default: "."
  omit: string;   // default: "!"
  mutate: string; // default: "~"
  glob: string;   // default: "*"
}
```

Example:

```ts
import { DefaultGrammar } from "ts-deep-pick";

interface G extends DeepPickGrammar {
  props: ":"; // Now use ":" as property separator
}

// Result type: { foo: { a: number } };
type FooWithJustA = DeepPick<FooBar, "foo:a", G>;

```

### `AmbiguousProp<G = DefaultGrammar>`

Returns the template literal type that represents any string that -- if used
as a property name -- would be ambigous given the set of character tokens.

```ts
// Result type: "a"
// ("!a" is ambiguous because of the "!" character).
// ("a.b" is ambiguous because of the "." character)
type ValidProps = Exclude<"a" | "!a" | "a.b", AmbiguousProps>

// Result type: "a" | "a.b"
// ("a:b" would have been ambiguous now because of custom grammar G defined
// earlier).
type ValidProps = Exclude<"a" | "!a" | "a.b", AmbiguousProps<G>>
```
