Skip to content

Commit

Permalink
Revision 0.32.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Dec 5, 2023
1 parent dc48ae7 commit e9eb0ac
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 16 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.32.0-dev-13",
"version": "0.32.0-dev-14",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
Expand Down
90 changes: 77 additions & 13 deletions src/type/mapped/mapped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/

import type { TSchema } from '../schema/index'
import type { Ensure, Evaluate } from '../helpers/index'
import type { Ensure, Evaluate, Assert } from '../helpers/index'
import { TSchema as IsSchemaType } from '../guard/type'
import { Kind, OptionalKind, ReadonlyKind } from '../symbols/index'
import { CloneType } from '../clone/type'
Expand All @@ -41,15 +41,14 @@ import { IndexPropertyKeys, type TIndexPropertyKeys } from '../indexed/index'
import { Intersect, type TIntersect } from '../intersect/index'
import { Iterator, type TIterator } from '../iterator/index'
import { Literal, type TLiteral, type TLiteralValue } from '../literal/index'
import { Never, type TNever } from '../never/index'
import { Object, type TObject, type TProperties, type ObjectOptions } from '../object/index'
import { Optional, type TOptional } from '../optional/index'
import { Promise, type TPromise } from '../promise/index'
import { Readonly, type TReadonly } from '../readonly/index'
import { Tuple, type TTuple } from '../tuple/index'
import { Union, type TUnion } from '../union/index'
// mapping types
import type { TMappedResult } from './mapped-result'
import { MappedResult, type TMappedResult } from './mapped-result'
import type { TMappedKey } from './mapped-key'
// prettier-ignore
import {
Expand All @@ -60,7 +59,6 @@ import {
TIntersect as IsIntersectType,
TIterator as IsIteratorType,
TReadonly as IsReadonlyType,
TLiteralValue as IsLiteralValueType,
TMappedResult as IsMappedResultType,
TMappedKey as IsMappedKeyType,
TObject as IsObjectType,
Expand All @@ -72,35 +70,101 @@ import {

// ------------------------------------------------------------------
// FromMappedResult
//
// We only evaluate the context (K) if it is a keyof P. Otherwise we
// remap the back to a MappedResult with the expectation it will be
// evaluated by an outer context. Note that overlapping keys in the
// outer context may result in incorrect evaluation of the outer
// context. Reproduction code below.
//
// [reproduction]
//
// type S = {
// [L in 'A' | 'B']: {
// [R in 'B' | 'C']: L
// }
// }
//
// [correct evaluation]
//
// type S = {
// A: {
// B: "A";
// C: "A";
// };
// B: {
// B: "B";
// C: "B";
// };
// }
//
// [errored evaluation (overlapping B)]
//
// type T = {
// A: {
// B: "B"; // error
// C: "A";
// };
// B: {
// B: "B";
// C: "B";
// };
// }
//
// ------------------------------------------------------------------
// prettier-ignore
type FromMappedResult<K extends PropertyKey, P extends TProperties> = (
K extends keyof P
? FromSchemaType<K, P[K]>
: TNever
: TMappedResult<P>
)
// prettier-ignore
function FromMappedResult<K extends PropertyKey, P extends TProperties>(K: K, P: P): FromMappedResult<K, P> {
return (
K in P
? FromSchemaType(K, P[K as string])
: Never()
: MappedResult(P)
) as FromMappedResult<K, P>
}
// ------------------------------------------------------------------
// FromMappedKey
//
// Here we remap MappedKey into MappedResult and have it evaluated
// via FromMappedResult using the context key (K). This is required
// in cases where the caller is nesting Mapped types and attempts to
// collect on the outer context (K) which is unknown to the inner
// context. The FromMappedResult only evaluates if (K) is a keyof
// (P), otherwise it remaps itself back into a MappedResult to be
// evaluated by the outer Mapped context. See above.
//
// ------------------------------------------------------------------
// prettier-ignore
type FromMappedKey<K extends PropertyKey, _ extends PropertyKey[]> = (
K extends TLiteralValue
? TLiteral<K>
: TNever
type MappedKeyToMappedResultProperties<K extends PropertyKey, P extends PropertyKey[]> = (
P extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]]
? { [_ in L]: TLiteral<Assert<L, TLiteralValue>> } & MappedKeyToMappedResultProperties<K, R>
: {}
)
// prettier-ignore
function MappedKeyToMappedResultProperties<K extends PropertyKey, P extends PropertyKey[]>(K: K, P: [...P]): MappedKeyToMappedResultProperties<K, P> {
const [L, ...R] = P
return (
P.length > 0
? { [L]: Literal(L as string), ...MappedKeyToMappedResultProperties(K, R) }
: {}
) as MappedKeyToMappedResultProperties<K, P>
}
// prettier-ignore
type FromMappedKey<
K extends PropertyKey,
P extends PropertyKey[],
R extends TProperties = MappedKeyToMappedResultProperties<K, P>
> = (
FromMappedResult<K, R>
)
// prettier-ignore
function FromMappedKey<K extends PropertyKey, P extends PropertyKey[]>(K: K, P: [...P]) {
return IsLiteralValueType(K)
? Literal(K)
: Never()
const R = MappedKeyToMappedResultProperties(K, P)
return FromMappedResult(K, R)
}
// ------------------------------------------------------------------
// FromRest
Expand Down

0 comments on commit e9eb0ac

Please sign in to comment.