Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions technical-reports/resolver/bundling.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Given a resolver that references 5 files:
}
}
},
"composition": [
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/modifiers/theme" }
]
Expand Down Expand Up @@ -85,7 +85,7 @@ One could inline the contents, resulting in:
}
}
},
"composition": [
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/modifiers/theme" }
]
Expand All @@ -100,9 +100,11 @@ Note that `foundation/colors.json` was referenced 3 times in the document, so in

## Using $defs for files

As described in [$defs](#defs), `$defs` don’t have defined behavior in a resolver document. But they are valid pointers for [reference objects](#reference-objects). This strategy involves creating a top-level `$defs` key, with each top-level key containing the contents for that file.
As described in [$defs](#defs), `$defs` don’t have defined behavior in a resolver document. They may only be used if a tool decides to support this feature of JSON Schema.

There is no downside to using `$defs` other than the possibility of some tools not supporting it, since `$defs` is not a requirement of this spec.
This strategy involves creating a top-level `$defs` key, with each top-level key containing the contents for that file.

The only downside of using `$defs` is some tools may choose to ignore it, as it is not a minimum requirement of a resolver document.

<aside class="example" title="Bundling by using $defs">

Expand Down Expand Up @@ -133,7 +135,7 @@ Given the same resolver [from the inlining section](#inlining-files), we can cre
}
}
},
"composition": [
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/modifiers/theme" }
],
Expand Down
2 changes: 1 addition & 1 deletion technical-reports/resolver/inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Given the following modifiers:

```json
{
"composition": [
"resolutionOrder": [
{
"type": "modifier",
"name": "theme",
Expand Down
18 changes: 9 additions & 9 deletions technical-reports/resolver/resolution-logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,32 @@
Tools MUST handle the resolution stages in order to produce the correct output.

1. [Input validation](#input-validation): verifying the [input](#inputs) is valid for the given resolver document.
2. [Composition](#composition-0): tracing the `composition` order to produce the final tokens structure.
2. [Ordering](#ordering): tracing the `resolutionOrder` array to produce the final tokens structure.
3. [Aliases](#aliases): resolving token aliases in the final tokens structure.
4. [Resolution](#resolution-0): the final end result

## Input validation

Tools MUST require all [=inputs=] match the provided [modifier](#modifiers) contexts.

If a resolver does NOT declare any modifiers, skip this step and proceed to [Composition](#composition-0).
If a resolver does NOT declare any modifiers, skip this step and proceed to [ordering](#ordering).

1. For every key in the input object:
1. Verify it corresponds with a valid modifier. If it does not, throw an error.
1. Verify that key’s value corresponds with that modifier’s allowed values. If it does not, throw an error.
2. For every modifier in the resolver:
1. If that resolver does NOT declare a default value, verify a key is provided in the input. If not, throw an error.

## Composition
## Ordering

Tools MUST iterate over the [composition](#composition) array in order.
Tools MUST iterate over the [resolutionOrder](#resolution-order) array in order.

1. For every item in the array, determine whether it’s a [set](#sets) or [modifier](#modifiers), flattening into a single tokens structure in array order.
1. If the item is a set, combine the `sources` in array order to produce a single tokens structure.
1. Otherwise, if the item is a modifier, select only the `context` that matches the [input](#inputs), combining the array in order to produce a single tokens structure.
1. In case of a conflict, take the most recent occurrence in the array.

1. Repeat until you’ve reached the end of the composition array.
1. Repeat until you’ve reached the end of the `resolutionOrder` array.

The final result will be a tokens structure that behaves the same as if it were one source to begin with.

Expand Down Expand Up @@ -65,7 +65,7 @@ The final result will be a tokens structure that behaves the same as if it were
]
}
},
"composition": [{ "$ref": "#/sets/foundation" }]
"resolutionOrder": [{ "$ref": "#/sets/foundation" }]
}
```

Expand All @@ -90,7 +90,7 @@ Here, two `color.text.default` tokens were encountered. Since order matters, the

Aliases MUST NOT be resolved until this step.

After the [composition](#composition-0) has been flattened into a single tokens structure, the only remaining step is resolving aliases. Aliases are resolved the exact same way as outlined in the [format](../format/#aliases-references):
After the [ordering](#ordering) has been flattened into a single tokens structure, the only remaining step is resolving aliases. Aliases are resolved the exact same way as outlined in the [format](../format/#aliases-references):

- Deep aliases are allowed, so long as they’re not circular
- An alias must point to the correct `$type`.
Expand Down Expand Up @@ -128,7 +128,7 @@ Resolver
}
}
},
"composition": [
"resolutionOrder": [
{ "$ref": "#/sets/foundation" },
{ "$ref": "#/sets/components" },
{ "$ref": "#/modifiers/theme" }
Expand Down Expand Up @@ -184,7 +184,7 @@ themes/dark.json
1. Input Validation
1. Verify that `theme` is a defined modifier (it passes).
1. Verify that `dark` is a valid value for the `theme` modifier (it passes).
1. Composition
1. Ordering
1. The first item is the `foundation` set, providing `color.brand.primary`.
2. The second item is the `components` set, providing `button.background` and `button.padding`.
3. The third and final item is the `theme` modifier, providing `theme.accent`.
Expand Down
68 changes: 35 additions & 33 deletions technical-reports/resolver/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

A resolver document contains the following properties at the root level:

| Name | Type | Required | Description |
| :------------------------------ | :--------------------- | :------: | :---------------------------------------------- |
| [**name**](#name) | `string` | | A short, human-readable name for the document. |
| [**version**](#version) | `YYYY-MM-DD` | Y | Version. Must be `2025-10-01`. |
| [**description**](#description) | `string` | | A human-readable description for this document. |
| [**sets**](#sets) | Map[`string`, Set] | | Definition of sets. |
| [**modifiers**](#modifiers) | Map[`string, Modifier] | | Definition of modifiers. |
| [**composition**](#composition) | `ReferenceObject[]` | Y | Resolution of sets and modifiers. |
| Name | Type | Required | Description |
| :-------------------------------------- | :--------------------------------------- | :------: | :---------------------------------------------- |
| [**name**](#name) | `string` | | A short, human-readable name for the document. |
| [**version**](#version) | `YYYY-MM-DD` | Y | Version. Must be `2025-10-01`. |
| [**description**](#description) | `string` | | A human-readable description for this document. |
| [**sets**](#sets) | Map[`string`, Set] | | Definition of sets. |
| [**modifiers**](#modifiers) | Map[`string, Modifier] | | Definition of modifiers. |
| [**resolutionOrder**](#resolutionOrder) | `(ReferenceObject \| Set \| Modifier)[]` | Y | Resolution of sets and modifiers. |

### Name

Expand Down Expand Up @@ -200,13 +200,13 @@ The number of possible [=resolutions=] a document may generate may be predicted

</aside>

### Composition
### Resolution Order

The `composition` key is an array of [sets](#sets) and [modifiers](#modifiers) ordered to produce the final result of tokens. The order is significant, with tokens later in the array overriding any tokens that came before them, in case of conflict.
The `resolutionOrder` key is an array of [sets](#sets) and [modifiers](#modifiers) ordered to produce the final result of tokens. The order is significant, with tokens later in the array overriding any tokens that came before them, in case of conflict.

<aside class="example" title="Composing sets and modifiers together">

Given a `composition` that consists of multiple sets and modifiers:
Given a `resolutionOrder` that consists of multiple sets and modifiers:

```json
{
Expand Down Expand Up @@ -239,7 +239,7 @@ Given a `composition` that consists of multiple sets and modifiers:
"default": "light"
}
},
"composition": [
"resolutionOrder": [
{ "$ref": "#/sets/size" },
{ "$ref": "#/sets/typography" },
{ "$ref": "#/sets/animation" },
Expand Down Expand Up @@ -328,7 +328,7 @@ Modifiers MAY contain empty context arrays:
}
}
},
"composition": [
"resolutionOrder": [
// …
{ "$ref": "#/modifiers/debug" }
]
Expand All @@ -341,26 +341,26 @@ This would then load an additional `debug.json` file if it received a `{ "debug"

#### Inline sets and modifiers

In `composition`, a [set](#sets) or [modifier](#modifiers) MAY be declared inline, so long as `name` and `type` keys are added to the object:
In `resolutionOrder`, a [set](#sets) or [modifier](#modifiers) MAY be declared inline, so long as `name` and `type` keys are added to the object:

| Property | Type | Required | Description |
| :------- | :-------------------- | :------: | :---------------------------------------------------------------------------- |
| **name** | `string` | Y | A unique name that MUST NOT conflict with any other `name` in `compositions`. |
| **type** | `"set" \| "modifier"` | Y | MUST be `"set"` or `"modifier"` according to the type. |
| Property | Type | Required | Description |
| :------- | :-------------------- | :------: | :------------------------------------------------------------------------------- |
| **name** | `string` | Y | A unique name that MUST NOT conflict with any other `name` in `resolutionOrder`. |
| **type** | `"set" \| "modifier"` | Y | MUST be `"set"` or `"modifier"` according to the type. |

Tools MUST throw an error in the case where `name` or `type` are missing from an inline object, or if `name` is duplicated among any objects.

<aside class="ednote" title="Name">

When sets and modifiers appear in their respective root level `sets` and `modifiers` keys, it is valid for a set to share a name with a modifier. It is only invalid to duplicate a `name` inside the `composition` array.
When sets and modifiers appear in their respective root level `sets` and `modifiers` keys, it is valid for a set to share a name with a modifier. It is only invalid to duplicate a `name` inside the `resolutionOrder` array.

</aside>

<aside class="example" title="Composition with inline sets and modifiers">
<aside class="example" title="Resolution Order with inline sets and modifiers">

```json
{
"composition": [
"resolutionOrder": [
{
"type": "set",
"name": "Size",
Expand Down Expand Up @@ -400,25 +400,25 @@ When sets and modifiers appear in their respective root level `sets` and `modifi

</aside>

Inline sets and modifiers MUST NOT be referenced in any way. Tools SHOULD throw an error when a [reference object](#reference-objects) points to a composition item.
Inline sets and modifiers MUST NOT be referenced in any way. Tools SHOULD throw an error when a [reference object](#reference-objects) points to a resolution order item.

<aside class="example" title="Invalid inline set reference">

The following [reference object](#reference-objects) pointer is invalid regardless of where it appears:

```json
{ "$ref": "#/composition/4" }
{ "$ref": "#/resolutionOrder/4" }
```

This is very likely to create an invalid reference, no matter if it appears in [sets](#sets) (circular dependency), [modifiers](#modifiers) (circular dependency), or in another place in the [composition](#composition) array (infinite recursion). The times where this would not cause an invalid reference are rare, and the slightest change may turn it into a circular reference.
This is very likely to create an invalid reference, no matter if it appears in [sets](#sets) (circular dependency), [modifiers](#modifiers) (circular dependency), or in another place in the [resolutionOrder](#resolution-order) array (infinite recursion). The times where this would not cause an invalid reference are rare, and the slightest change may turn it into a circular reference.

</aside>

<section class="informative">

#### Ordering of sets and modifiers

The `composition` array allows for any ordering of sets and modifiers to the user’s choosing. However, in the scenario that many sets must appear after the modifiers to resolve conflicts, it is likely a [smell](https://en.wikipedia.org/wiki/Code_smell) of unpredictable and brittle token organization. Ideally, modifiers handle conditional values so well they require few or no overrides (see [orthogonality](#orthogonal-orthogonality)). In practical terms, this means that
The `resolutionOrder` array allows for any ordering of sets and modifiers to the user’s choosing. However, in the scenario that many sets must appear after the modifiers to resolve conflicts, it is likely a [smell](https://en.wikipedia.org/wiki/Code_smell) of unpredictable and brittle token organization. Ideally, modifiers handle conditional values so well they require few or no overrides (see [orthogonality](#orthogonal-orthogonality)). In practical terms, this means that

</section>

Expand All @@ -430,11 +430,13 @@ Tools MUST resolve all reference objects in a resolver document.

Reference objects MUST NOT be circular, neither referencing other pointers that reference itself, nor referencing any parent node of the reference object.

Tools MUST support same-document reference objects, as that is the minimum requirement to support this resolver module. Beyond that, tools may make individual decisions to not support some URI types such as importing files on a local file system, or remote URIs over TCP/IP.

<aside class="example" title="Valid reference objects">

Common examples of reference objects include:

<table><thead><tr><th>Code</th><th>Result</th></tr></thead><tbody><tr><th>
<table><thead><tr><th>Code</th><th>Result</th><th>Required Support</th></tr></thead><tbody><tr><th>

```json
{ "$ref": "#/sets/MySet" }
Expand All @@ -444,7 +446,7 @@ Common examples of reference objects include:

References `sets/MySet` within the same document.

</td></tr><tr><th>
</td><td>Y</td></tr><tr><th>

```json
{ "$ref": "path/to/example.json" }
Expand All @@ -454,7 +456,7 @@ References `sets/MySet` within the same document.

References the entire contents of `./path/to/example.json`, relative to this document.

</td></tr><tr><th>
</td><td></td></tr><tr><th>

```json
{ "$ref": "example.json#sets/MySet" }
Expand All @@ -464,7 +466,7 @@ References the entire contents of `./path/to/example.json`, relative to this doc

References only a part of `example.json`: `sets/MySet`.

</td></tr><tr><th>
</td><td></td></tr><tr><th>

```json
{ "$ref": "https://my-server.com/tokens.json" }
Expand All @@ -474,7 +476,7 @@ References only a part of `example.json`: `sets/MySet`.

References a remote file hosted at a publicly-available URL.

</td></tr></tbody></table>
</td><td></td></tr></tbody></table>

</aside>

Expand Down Expand Up @@ -511,10 +513,10 @@ A single reference object that references its parent is invalid because it will

A pointer MAY point anywhere within the same document, with the exception of the following:

1. Only [composition](#composition) may reference a modifier (`#/modifiers/…`). Sets and modifiers MUST NOT reference another modifier.
1. Only [resolutionOrder](#resolution-order) may reference a modifier (`#/modifiers/…`). Sets and modifiers MUST NOT reference another modifier.
- Referencing a modifier from a set could cause [inputs](#inputs) to apply conditional logic to a structure that can’t support it, therefore it’s not allowed.
- Referencing a modifier from another modifier would mean a single input applies to unexpected modifiers, therefore it’s not allowed.
1. A reference object MUST NOT point to anything in the [composition](#composition) array (`#/composition/…`). Composition, by its nature, references many other parts of the document. Duplicating any part of composition will produce complex, hard-to-predict chains.
1. A reference object MUST NOT point to anything in the [resolutionOrder](#resolution-order) array (`#/resolutionOrder/…`). Resolution ordering, by its nature, references many other parts of the document. Duplicating any part of this will produce complex, hard-to-predict chains.

Tools MUST throw an error if encountering any invalid pointers.

Expand Down Expand Up @@ -589,7 +591,7 @@ Here is an example where a set contains arbitrary metadata for the `figma.com` v

## $defs

Tools MAY allow defining structures in JSON Schema [$defs](https://json-schema.org/understanding-json-schema/structuring#defs) but is not a requirement, and ultimately is up to the tool to decide.
Tools MAY allow defining structures in JSON Schema [$defs](https://json-schema.org/understanding-json-schema/structuring#defs) but is not a requirement. However, tools MUST NOT throw an error when encountering `$defs`, and MUST ignore the key if it is not supported.

<aside class="ednote" title="$defs">

Expand Down
6 changes: 3 additions & 3 deletions technical-reports/resolver/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<section class="informative">

## <dfn>Orthogonal</dfn> (orthogonality)
## Orthogonality

A trait describing two or more contexts that each operate on exclusive tokens, i.e. do not overlap. Modifiers MAY be orthogonal, but it are not required to be.

Expand All @@ -21,6 +21,6 @@ Since both modifiers provide a value for the `color.button` token, this means ar

</section>

## <dfn>Resolution</dfn>
## Permutation

A resolution is a single possible permutation of a resolver document. A resolution maps 1:1 to an [input](#inputs), but the difference is the “input” refers to the [modifier](#modifiers) contexts used, whereas “resolution” refers to the final set of tokens and token values produced.
A permutation is a single possible permutation of a resolver document. A permutation maps 1:1 to an [input](#inputs), but the term “input” emphasizes the [modifier](#modifiers) contexts used, where “permutation” emphasizes the final set of tokens.