<p dir="auto"><strong>TypeScript Version:</strong></p>
<p dir="auto">1.8.10</p>
<p dir="auto"><strong>Code</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="type StringEnum = &quot;one&quot; | &quot;two&quot;

interface IndexedObject {
  [index: StringEnum]: boolean
}
"><pre class="notranslate"><span class="pl-k">type</span> <span class="pl-smi">StringEnum</span> <span class="pl-c1">=</span> <span class="pl-s">"one"</span> <span class="pl-c1">|</span> <span class="pl-s">"two"</span>

<span class="pl-k">interface</span> <span class="pl-smi">IndexedObject</span> <span class="pl-kos">{</span>
  <span class="pl-kos">[</span><span class="pl-s1">index</span>: <span class="pl-smi">StringEnum</span><span class="pl-kos">]</span>: <span class="pl-smi">boolean</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto"><strong>Expected behavior:</strong><br>
No error</p>
<p dir="auto"><strong>Actual behavior:</strong><br>
error TS1023: An index signature parameter type must be 'string' or 'number'.</p>
<h1 dir="auto">Problem</h1>
<p dir="auto">In the <a href="https://github.com/cyclejs">cycle.js</a> project, data-flow is implemented by defining functions of the type:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="type CreateDataFlow&lt;Source, Sink&gt; = (sources: Source)=&gt;Sink"><pre class="notranslate"><span class="pl-k">type</span> <span class="pl-smi">CreateDataFlow</span><span class="pl-c1">&lt;</span><span class="pl-smi">Source</span><span class="pl-kos">,</span> <span class="pl-smi">Sink</span><span class="pl-c1">&gt;</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">sources</span>: <span class="pl-smi">Source</span><span class="pl-kos">)</span><span class="pl-c1">=&gt;</span><span class="pl-smi">Sink</span></pre></div>
<p dir="auto">Where <code class="notranslate">Source</code> and <code class="notranslate">Sink</code> are interfaces where every member is a property of type <code class="notranslate">Observable</code>.</p>
<p dir="auto">Each implementation of <code class="notranslate">CreateDataFlow</code> will specify Interfaces (typically without index types) for <code class="notranslate">Source</code> and <code class="notranslate">Sink</code>.</p>
<p dir="auto">Currently, it is not possible to specify this contract using types.</p>
<p dir="auto">This is a problem because the contract is not enforced by types, and instead relies on the developer to implement their data-flow function correctly. More specifically, this is a problem when dealing with higher-order code that takes implementations of <code class="notranslate">CreateDataFlow</code>, that wants to deal with the input/output generically (i.e. without knowing what the keys/members are at compile time, it wishes to iterate over them knowing only that the type is <code class="notranslate">Observable</code>).</p>
<h1 dir="auto">Existing Solutions</h1>
<p dir="auto">There are two options:</p>
<p dir="auto">The first is the one illustrated above. I.e., where the type-parameters <code class="notranslate">Source</code> and <code class="notranslate">Sink</code> have no type-restrictions. This creates the situation where any function that takes a parameter will be accepted. It's not clear from the typing what the contract should be, and so it will be trivially easy to pass in an invalidly typed function. If we wish to iterate over the output of the implementation, for example, we have to cast it to an index type (although, type guards do at least make this runtime safe).</p>
<p dir="auto">The second option is to try something like:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="type CreateDataFlow&lt;Source extends {[key:string]:Observable&lt;any&gt;}, Sink extends {[key:string]:Observable&lt;any&gt;}&gt; = (source:Source)=&gt;Sink"><pre class="notranslate"><span class="pl-k">type</span> <span class="pl-smi">CreateDataFlow</span><span class="pl-c1">&lt;</span><span class="pl-smi">Source</span> <span class="pl-k">extends</span> <span class="pl-kos">{</span><span class="pl-kos">[</span><span class="pl-s1">key</span>:<span class="pl-smi">string</span><span class="pl-kos">]</span>:<span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi">any</span><span class="pl-kos">&gt;</span><span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-smi">Sink</span> <span class="pl-k">extends</span> <span class="pl-kos">{</span><span class="pl-kos">[</span><span class="pl-s1">key</span>:<span class="pl-smi">string</span><span class="pl-kos">]</span>:<span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi">any</span><span class="pl-kos">&gt;</span><span class="pl-kos">}</span><span class="pl-c1">&gt;</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">source</span>:<span class="pl-smi">Source</span><span class="pl-kos">)</span><span class="pl-c1">=&gt;</span><span class="pl-smi">Sink</span></pre></div>
<p dir="auto">The problem is, this doesn't actually adhere to the contract: If we try to use an implementation where <code class="notranslate">Source</code> is an Interface (with all members having type <code class="notranslate">Observable</code>), an error is thrown by the compiler. Using this approach we are forced to weaken the contract to index-types only, which means we cannot differentiate (at a type-level) between implementations, and indeed allows callers of an implementation of <code class="notranslate">CreateDataFlow</code> to pass in any conforming instance (although, again type-guards do make this runtime safe).</p>
<p dir="auto">Neither solution adequately captures the contract, and requires use of type-guards to make type-safe. Needless to say, runtime type-safety is not nearly as nice as compile time, otherwise we'd all be using JavaScript instead.</p>
<h1 dir="auto">Proposal: Allow string literals in index types</h1>
<p dir="auto">The proposal is that the following code would be valid:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="type ObservableMap &lt;Keys extends string&gt; = {[keys:Keys]: Observable&lt;any&gt;}"><pre class="notranslate"><span class="pl-k">type</span> <span class="pl-smi">ObservableMap</span> <span class="pl-c1">&lt;</span><span class="pl-smi">Keys</span> <span class="pl-k">extends</span> <span class="pl-smi">string</span><span class="pl-c1">&gt;</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span><span class="pl-kos">[</span><span class="pl-s1">keys</span>:<span class="pl-smi">Keys</span><span class="pl-kos">]</span>: <span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi">any</span><span class="pl-kos">&gt;</span><span class="pl-kos">}</span></pre></div>
<p dir="auto">And so the definition of <code class="notranslate">CreateDataFlow</code> becomes:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="type CreateDataFlow&lt;SourceKeys, SinkKeys&gt; = (source: ObservableMap&lt;SourceKeys&gt;) =&gt; ObservableMap&lt;SinkKeys&gt;"><pre class="notranslate"><span class="pl-k">type</span> <span class="pl-smi">CreateDataFlow</span><span class="pl-c1">&lt;</span><span class="pl-smi">SourceKeys</span><span class="pl-kos">,</span> <span class="pl-smi">SinkKeys</span><span class="pl-c1">&gt;</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">source</span>: <span class="pl-smi">ObservableMap</span><span class="pl-kos">&lt;</span><span class="pl-smi">SourceKeys</span><span class="pl-kos">&gt;</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-smi">ObservableMap</span><span class="pl-kos">&lt;</span><span class="pl-smi">SinkKeys</span><span class="pl-kos">&gt;</span></pre></div>
<p dir="auto">Sub-types would be interfaces, and Key would be a string-literal defining the members, e.g.:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="interface MySource {
  a: Observable&lt;string&gt;
  b: Observable&lt;number&gt;
}
type MySourceKey = 'a'|'b'

interface MySink {
  c: Observable&lt;boolean&gt;
  d: Observable&lt;void&gt;
}
type MySinkKey = 'c'|'d'

const myDataFlow: CreateDataFlow&lt;MySourceKey, MySinkKey&gt; = (mySource: MySource)=&gt;MySink {
  // ...
}
"><pre class="notranslate"><span class="pl-k">interface</span> <span class="pl-smi">MySource</span> <span class="pl-kos">{</span>
  <span class="pl-c1">a</span>: <span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi">string</span><span class="pl-kos">&gt;</span>
  <span class="pl-c1">b</span>: <span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi">number</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">}</span>
<span class="pl-k">type</span> <span class="pl-smi">MySourceKey</span> <span class="pl-c1">=</span> <span class="pl-s">'a'</span><span class="pl-c1">|</span><span class="pl-s">'b'</span>

<span class="pl-k">interface</span> <span class="pl-smi">MySink</span> <span class="pl-kos">{</span>
  <span class="pl-c1">c</span>: <span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi">boolean</span><span class="pl-kos">&gt;</span>
  <span class="pl-c1">d</span>: <span class="pl-smi">Observable</span><span class="pl-kos">&lt;</span><span class="pl-smi"><span class="pl-k">void</span></span><span class="pl-kos">&gt;</span>
<span class="pl-kos">}</span>
<span class="pl-k">type</span> <span class="pl-smi">MySinkKey</span> <span class="pl-c1">=</span> <span class="pl-s">'c'</span><span class="pl-c1">|</span><span class="pl-s">'d'</span>

<span class="pl-k">const</span> <span class="pl-en">myDataFlow</span>: <span class="pl-smi">CreateDataFlow</span><span class="pl-kos">&lt;</span><span class="pl-smi">MySourceKey</span><span class="pl-kos">,</span> <span class="pl-smi">MySinkKey</span><span class="pl-kos">&gt;</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">mySource</span>: <span class="pl-smi">MySource</span><span class="pl-kos">)</span><span class="pl-c1">=&gt;</span><span class="pl-smi">MySink</span> <span class="pl-kos">{</span>
  <span class="pl-c">// ...</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">Potentially something akin to <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="144197140" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/7722" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/7722/hovercard" href="https://github.com/microsoft/TypeScript/issues/7722">#7722</a> could be used to manage these "key" definitions.</p>