# Retrieving and working with the tokens

## Get a `SDTFClient`&#x20;

The [`SDTFClient`](https://docs.specifyapp.com/reference/specify-sdk/sdtfclient) is a class providing numerous methods to work with the design data stored using the Specify Design Token Format (SDTF).

**It's the way of interacting with your tokens and generating content.**

```typescript
// Fetch a Repository
const SPECIFY_REPOSITORY_NAME = "MY-REPOSITORY-NAME";
const sdtfClient = await specifyClient.getSDTFClientByRepositoryName(
  SPECIFY_REPOSITORY_NAME,
);

console.log(`Fetched repository: ${sdtfClient.repository.name}`);
```

#### Get the SDTF JSON token tree

You would want to grab the SDTF JSON token tree of a repository anytime you want to: send the token tree over the network or debug an intermediate manipulation.

```typescript
const jsonTokenTree = sdtfClient.getJSONTokenTree();

console.log(jsonTokenTree) // the object literal representation of the Specify Design Token data
```

Now that we know how to retrieve our SDTF, let's see how we can manipulate and retrieve our tokens.

## Retrieving the tokens

There's multiple way to retrieve a token. If you want to work with a specific one you'll probably want to only get one, if you're developing a generic solution, you'll probably prefer iterating over many tokens. Let's have a look on how to do this.

### Retrieving a single token

To retrieve a single token, you can just pick it by its path:

```typescript
const tokenState = sdtfClient.getTokenState(['path', 'to', 'token'])
```

### Retrieving all the tokens

#### Tokens

```typescript
const tokens = sdtfClient.getAllTokenStates()
```

#### Groups

```typescript
const groups = sdtfClient.getAllGroupStates()
```

#### Collections

```typescript
const collections = sdtfClient.getAllCollectionStates()
```

### Retrieving specific tokens

If you need specifically some tokens, for example, based on the name, type, path, etc... You can perform a query and map over the results:

```typescript
const results = sdtfClient
  .mapQueryResults(
    { where: { collection: '^My Collection$', select: true, children: { tokens: true } } },
    (treeNodeState, engine, queryResult) => {
      if (treeNodeState.isCollection) {
        return treeNodeState.name
      }
      
      if (treeNodeState.isToken) {
        return treeNodeState.stringPath
      }
      
      return undefined
    },
  );
```

Or, you can only iterate over the results of your query if you need to perform mutations for example:

```typescript
sdtfClient
  .forEachQueryResult(
    { where: { token: '.*', select: true } },
    (treeNodeState, engine, queryResult) => {
      if (treeNodeState.isToken) {
        engine.mutation.renameToken({
          atPath: treeNodeState.path,
          name: treeNodeState.name.toLowerCase(),
        });
      }
    },
  );
```

{% hint style="info" %}
Looking for details about the query? 👉 heads up to the [SDTF Query Language](https://docs.specifyapp.com/concepts/sdtf-client/sdtf-engine#sdtf-query-language) concept.
{% endhint %}

{% hint style="info" %}
Looking for more methods to retrieve tokens? 👉 heads up to the [SDTFEngine](https://docs.specifyapp.com/reference/sdtf-engine#query-methods) reference.
{% endhint %}

## The token architecture

The `TokenState` instance will be your main access to your tokens. Through this, you can retrieve your token data or perform updates. But first, let’s put some context and try to figure out what this `TokenState` is trying to solve.

If we take a look at [the token representation in the SDTF](https://docs.specifyapp.com/concepts/specify-design-token-format), it’s basically a JSON value where a lot of keys can be an alias rather than a value. Let’s have a look at all the possible cases with an example. Note that the following examples are 4 different ways of achieving the same result from a value point of view.

### A basic token

```tsx
{
  mySize: {
    $type: 'dimension',
    $value: {
      small: { unit: 'px', value: 12 },
      large: { unit: 'px', value: 24 },
    }
  }
}
```

### An alias at the value level

```tsx
{
  sizeValue: {
    $type: 'number',
    $value: {
      default: 12
    }
  },
  mySize: {
    $type: 'dimension',
    $value: {
      small: {
	unit: 'px',
	value: { $alias: 'sizeValue', $mode: 'default' }
      },
      large: {
	unit: 'px',
	value: 24
      },
    }
  }
}
```

### An alias at the mode level

```tsx
{
  anotherSize: {
    $type: 'dimension',
    $value: {
      customMode: {
	unit: 'px',
	value: 12
      }
    }
  },
  mySize: {
    $type: 'dimension',
    $value: {
      small: { $alias: 'anotherSize', $mode: 'customMode' },
      large: {
	unit: 'px',
	value: 24
      },
    }
  }
}
```

### An alias at the top level

```tsx
{
  anotherSize: {
    $type: 'dimension',
    $value: {
      small: {
	unit: 'px',
	value: 12
       },
       large: {
         unit: 'px',
	 value: 24
       },
    }
  },
  mySize: {
    $type: 'dimension',
    $value: { $alias: 'anotherSize' },
  }
}
```

As you can see, there’s a lot of way to express a token, and dealing with all the possible cases is quite difficult. That’s why one of the goal of the `TokenState` is to help you to deal with everything.

## How to differentiate the tokens?

Before even thinking about retrieving the value of a token, we need to know what type of token we’re working with.

It’s actually quite easy to know as there’s a `type` property in the `TokenState` :

```tsx
console.log('Token type:', tokenState.type) // Token type: <type>
```

But even if we know the type, a problem will remain: Typescript doesn’t. `TokenState` is quite a generic class as the default type is something that looks like this: `TokenState<'dimension' | 'font' | 'breakpoint' | ...>` .

So the first mission is to be able to narrow down the type to be able to work with it. To do so, there’s a pretty convenient function:

```tsx
tokenState.matchByType(
  {
    // TokenState<'dimension'>
    dimension: (v) => { ... },
    // TokenState<'font'>
    font: (v) => { ... },
    // TokenState<'textStyle'>
    textStyle: (v) => { ... },
  },
  notMatched => { ... }
)
```

You can match as much type as you need, and if some are not matched, they’ll be caught by the callback in the 2nd argument.

If you're working with Typescript, you may face some type issues while using `matchByType`. A common example would be this:

```typescript
tokenState.matchByType(
  {
    dimension: _ => 1,
    breakpoint: _ => 'hello',
  },
  _ => undefined,
)
```

If you copy/paste this code inside your editor, Typescript will complain and gives you the following error: `Type '(_: TokenState<"breakpoint", Value, Mode>) => string' is not assignable to type '(token: TokenState<"breakpoint", Value, Mode>) => number | undefined'`.

Basically Typescript is unhappy that we return a `number` and a `string`. To fix the issue, there's 2 solutions:

1. Return the same type:

```typescript
tokenState.matchByType(
  {
    dimension: _ => 1.toString(),
    breakpoint: _ => 'hello',
  },
  _ => undefined,
)
```

2. Excplicitely set the return type:

```typescript
tokenState.matchByType<string | number>(
  {
    dimension: _ => 1,
    breakpoint: _ => 'hello',
  },
  _ => undefined,
)
```

Depending on the use case, you'll prefer the first over the second solution, or the second over the first.

## How to get the token value ?

Now that we know how to get the `TokenState` that we want, we want to extract the value from it. As mentioned before, a token can have a lot of places that can contains an alias, so when retrieving the value, there’s 2 ways of doing it.

### Ignoring aliasing

If you only want to get the raw value of a token and ignore the aliases, you can use the following function:

```tsx
tokenState.getJSONValue({
  resolveAliases: true,
  allowUnresolvable: false,
  targetMode: tokenState.modes[0],
})
```

Also, as combining `matchByType` and `getJSONValue` can become a bit heavy. For that case, you can use the following function:

```tsx
tokenState.matchJSONValueByType(
  {
    // { unit: 'px', value: 12 }
    dimension: (v, mode) => { ... },
    // { family: 'MyFont', weight: 700, ... }
    font: (v, mode) => { ... },
    // { lineHeight: { unit: 'px', value: 24 }, ... }
    textStyle: (v, mode) => { ... },
  },
  notMatched => { ... }
)
```

### Handling the aliases

#### Overview of the API

If you want to support the aliases in your output, you’ll need to handle them when they are some at the top level, mode level, or value level. To do so, we provide the Stateful Value API that helps you to make sure you handle all the possible cases. Let’s see an example and then break down everything.

```tsx
// TokenState<'dimension'> -> { value: 12, unit: 'px' }
tokenState
  .getStatefulValueResult()
  .mapResolvableTopLevelAlias(alias => { ... })
  .mapUnresolvableTopLevelAlias(alias => { ... })
  .mapTopLevelValue(modeLevel =>
    modeLevel
      .mapResolvableModeLevelAlias((alias, mode) => { ... })
      .mapUnresolvableModeLevelAlias(_ => { ... })
      .mapRawValue((rawValue, mode) => 
        rawValue
	  .value
	  .mapPrimitiveValue(value => { ... })
	  .mapResolvableValueLevelAlias(alias => { ... })
	  .mapUnresolvableValueLevelAlias(alias => { ... })				
      )
      .unwrap(),
  )
  .unwrap();

```

Although this API is a bit verbose, it’ll make sure that you cannot miss a case. So, what’s happening?

When you retrieve the value of a token, we put it inside a `StatefulResult` , and it’ll expose the following type union at each level:

* Top level: `ResolvableTopLevelAlias | UnresolvableTopLevelAlias | TopLevelValue`
* Mode level: `ResolvableModeLevelAlias | UnresolvableModeLevelAlias | RawValueSignature`
* Value level: `ResolvableValueLevelAlias | UnresolvableValueLevelAlias | string | number | ...`

Each mapping function is dedicated to handling one case.

First, you’ll need to handle the top level:

```typescript
tokenState
  .getStatefulValueResult()
  .mapResolvableTopLevelAlias(alias => { ... })
  .mapUnresolvableTopLevelAlias(alias => { ... })
  .mapTopLevelValue(modeLevel => { ... })
  .unwrap()
```

Note the `unwrap` at the end which is the way to extract the value.

Then you’ll want to handle the mode level:

```tsx
.mapTopLevelValue(modeLevel =>
  modeLevel
    .mapResolvableModeLevelAlias((alias, mode) => { ... })
    .mapUnresolvableModeLevelAlias(_ => { ... })
    .mapRawValue((rawValue, mode) => { ... })
    .unwrap()
```

All you need to do for the mode level is to return a value, and it'll be mapped to the right mode.

And finally the value level (we take a dimension token as an example):

```typescript
.mapRawValue((dimension, mode) => {
  const value = dimension
    .value
    // mapPrimitiveValue is not required if you only need the value inside
    // without updating it
    .mapPrimitiveValue(value => { ... })
    .mapUnresolvableValueLevelAlias(alias => { ... })
    .mapResolvableValueLevelAlias(alias => { ... })
    .unwrap()
    
    return value
})
```

{% hint style="warning" %}
If you call `mapPrimitiveValue`, it needs to be the first one to be called in order to avoid remapping results produced by `mapUnresolvableValueLevelAlias` or `mapResolvableValueLevelAlias`.
{% endhint %}

As you can see each step is always the same: handling unresolvable alias, resolvable alias and the value.

#### Resolving an alias

Sometimes, you need to use the precision of the stateful result API, but don't want to deal with the aliases at some levels. To avoid doing so, you can resolve an alias by calling `resolveDeepValue`, and you'll be left with handling the unresolvable alias and value case. Here is an example:

```typescript
tokenState
  .getStatefulValueResult()
  .resolveDeepValue()
  .mapUnresolvableTopLevelAlias(alias => ...)
  .mapTopLevelValue(modeLevel => 
    modeLevel
      .resolveDeepValue()
      .mapUnresolvableModeLevelAlias(alias => ...)
      .mapRawValue((dimension, mode) => 
        dimension
          .value
          .resolveDeepValue()
          .mapUnresolvableValueLevelAlias(alias => ...)
          .unwrap()
      )
      .unwrap()
  )
  .unwrap()
```

#### Handling only the value case

Although we could get rid of the resolvable alias case, handling the unresolvable case might still be too verbose. So let me introduce you to a nice pattern to only deal with values:

1. We will check if the token is fully resolvable
2. We'll use `resolveDeepValue` to remove the need of handling the resolvable aliases
3. We'll use `unwrapValue` to remove the need of handling the unresolvable case at the type level

```typescript
if (!tokenState.isFullyResolvable) {
  return
}

const output = tokenState
  .getStatefulValueResult()
  .resolveDeepValue()
  .mapTopLevelValue(modeLevel => 
    modeLevel
      .resolveDeepValue()
      .mapRawValue((dimension, mode) => 
        dimension
          .value
          .resolveDeepValue()
          .unwrapValue()
      )
      .unwrapValue()
  )
  .unwrapValue()
```

Thanks to this pattern, we went from a really verbose API to something lighter.
