Replies: 22 comments
-
|
The most straightforward solution I see is to add |
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hi! Thanks for reporting! (good to see users diving into some of TB's internals!)
At this stage, the Base types are somewhat at odds with Schema types and I haven't quite figured out how to reconcile them with the rest of the type system. At this stage, evaluating two intersected Base types will result in But you're on the right track with type R = Left extends Right ? true : false
//
// where: Left/Right is substituted with Base instancesThe issue tho is that TB needs a symmetric (runtime and type-level) implementation and I haven't figured out an API design that would achieve the type-level check, but I suspect HKT implementations may be required. With regards to API | HKT design, I have been exploring HKT implementations on another (albeit unrelated) project. Where the parser design for mapping AST structures has been working quite well, but where the callbacks "could" be repurposed for dynamic (or type level dynamic) Base type extends checks. TypeScript Link | Compilers in the Type System type Math = Wat<`(module
(func private_function (param $x f32) (param $y f32) (result f32) (result f32)
local.get $x
local.get $y
)
(func (export swap) (param $x f32) (param $y f32) (result f32) (result f32)
local.get $y
local.get $x
)
(func (export add) (param $x f32) (param $y f32) (result f32)
local.get $x
local.get $y
f32.add
)
(func (export sub) (param $x f32) (param $y f32) (result f32)
local.get $x
local.get $y
f32.sub
)
(func (export mul) (param $x f32) (param $y f32) (result f32)
local.get $x
local.get $y
f32.mul
)
(func (export div) (param $x f32) (param $y f32) (result f32)
local.get $x
local.get $y
f32.div
)
)`>Unfortunately, there's no established patterns for handling these kinds of things in the type system (this is on the upper end complex design work, so progress here will be slow). There won't be any significant changes to the TB type system before the end of the year (just stabilization work mostly) so will pick this one up next year .... but experimentation from the community is always welcome! Design suggestions too! I will drop a research tag on it for now. |
Beta Was this translation helpful? Give feedback.
-
|
@sinclairzx81 it turned out it was easier to fix. Now it will work |
Beta Was this translation helpful? Give feedback.
-
|
@sinclairzx81, just FYI, this is not a breaking change (unless someone relies on getting |
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hi, thanks for the PR and sorry for the delay (have been busy wrapping up work for the year) So have had a look through the PR tonight (thanks for this), but not too sure about the current interpretation of Base. The implementation provided performs a structural Equal comparison check on the Base type itself (which gets around Never), but I'm not sure if it's accurate to perform the Equal check on the Base type instances themselves... but rather the structure Base is trying to represent .... Base Represents Structural TypesThis is a more concrete example of what Base Extends would need to model for Reference Link. The goal would be to have Base behave the same as schematic based types, but where the mechanisms to achieve this are not clear (as of writing) import Type from 'typebox'
// -----------------------------------------------------------------
// (1) TypeScript: Structural Extends
// -----------------------------------------------------------------
{
type Vector3 = { x: number, y: number, z: number }
type Vector2 = { x: number, y: number }
type A = Vector3 extends Vector2 ? true : false // true
type B = Vector2 extends Vector3 ? true : false // false
}
// -----------------------------------------------------------------
// (2) TypeBox: Structural Extends
// -----------------------------------------------------------------
{
const { A, B } = Type.Script(`
type Vector3 = { x: number, y: number, z: number }
type Vector2 = { x: number, y: number }
type A = Vector3 extends Vector2 ? true : false
type B = Vector2 extends Vector3 ? true : false
`)
type A = Type.Static<typeof A> // true
type B = Type.Static<typeof B> // false
}
// -----------------------------------------------------------------
// (3) TypeBox: Base Extends Base
//
// Below Vector3 and Vector2 express a structural shape, but how
// should the Type.Base<...> describe Left / Right assignability?
//
// -----------------------------------------------------------------
{
class Vector3 extends Type.Base<{ x: number, y: number, z: number }> { /* ??? */ }
class Vector2 extends Type.Base<{ x: number, y: number }> { /* ??? */ }
const A = Type.Extends({}, new Vector2, new Vector3) // expect: TExtendsTrue
const B = Type.Extends({}, new Vector3, new Vector2) // expect: TExtendsFalse
}
// -----------------------------------------------------------------
// (4) TypeBox: Base Extends Schematic
// -----------------------------------------------------------------
{
class Vector3 extends Type.Base<{ x: number, y: number, z: number }> { /* ??? */ }
const Vector2 = Type.Script(`{ x: number, y: number }`)
const A = Type.Extends({}, Vector2, new Vector3) // expect: TExtendsTrue
const B = Type.Extends({}, new Vector3, Vector2) // expect: TExtendsFalse
}So in cases (3) and (4), we wouldn't perform a check against the
EqualLooking at the current Equals check, I'm not too sure this would work quite right without nominal typing support in TS. For example, the following Base types are structurally Equal. Ref: Nominal Typing in TypeScript (from 2014) microsoft/TypeScript#202 // These instances would be considered structurally Equal
class A extends Type.Base {}
class B extends Type.Base {}This said, there could be some utility in performing instance level extends checks (as per PR) but this would be somewhat orthogonal to the generic parameter structural requirement for Base itself. It is worth considering tho. ExtendsLeft / ExtendsRightI do think there needs to be a ExtendsLeft(...) and ExtendsRight(...) override for Base, and where the Left / Right implementation would need to try and derive associativity corresponding to the Base generic type ... but given the challenges, the current implementation is basically saying ....
The default of Unfortunately, I don't think I can take on the PR as it's only solving instance level Equals comparison on Base, but still happy to discuss more. Like most things in TypeBox, these kinds of designs tend to move at glacial speeds and there needs to be a lot of experimentation and design work done first. If I get a implementation wrong, it usually means I need to carry the implementation around for years (and there is some uncertainty if Base will be a long term type in the library given the reconciling challenges with structural types) But lets discuss more. |
Beta Was this translation helpful? Give feedback.
-
|
@sinclairzx81, thank you for the detailed response! I can see an easier option that may make everyone's lives much easier. What if class Vector3 extends Type.Base<{ x: number, y: number, z: number }> {
public constructor() {
super(Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}));
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Also, it's not a breaking change and is a progressive enhancement |
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hiya, again, sorry for the delay.
Hmmm, perhaps. I have been mulling over this issue, and the more I look at the Base type, the more I think the introduction of it in V1 was a mistake. TypeBox really wants to operate in a structural sense, and the requirement to add additional "things" to Base to make it integrate correctly seems to be an indication the type is not fitting the TBV1 design (at least not in the way I had hoped I could get it to work) Let's consider that ReverseStatic + interior Schema suggestion, and just to follow through on a train a thought. BaseThis could technically work where class Vector3 extends Type.Base<{ x: number, y: number, z: number }> {
public constructor() {
super(Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}));
}
}... but the natural extension of this would be the following.... class DateType extends Type.Base<Date> {
public constructor() {
super(Type.Object({
toString: Type.Function([], Type.String()),
getTime: Type.Function([], Type.Number()),
toISOString: Type.Function([], Type.String()),
toUTCString: Type.Function([], Type.String()),
// ...
}));
}
}... which just makes me wonder if just this would be reasonable ... const DateType = Type.Object({
toString: Type.Function([], Type.String()),
getTime: Type.Function([], Type.Number()),
toISOString: Type.Function([], Type.String()),
toUTCString: Type.Function([], Type.String()),
// ...
})... because it naturally integrates with the TB type system with no additional complexity Reference Link import Type from 'typebox'
const DateType = Type.Object({
toString: Type.Function([], Type.String()),
getTime: Type.Function([], Type.Number()),
toISOString: Type.Function([], Type.String()),
toUTCString: Type.Function([], Type.String()),
// ...
})
// Evaluation Works | Intersected Property
const Entry = Type.Evaluate(Type.Intersect([ // const Entry: Type.TObject<{
Type.Object({ created: DateType }), // created: Type.TObject<{
Type.Object({ created: DateType }), // toString: Type.TFunction<[], Type.TString>;
])) // getTime: Type.TFunction<[], Type.TNumber>;
// toISOString: Type.TFunction<[], Type.TString>;
// toUTCString: Type.TFunction<[], Type.TString>;
// }>;
// }>
function test(entry: Type.Static<typeof Entry>) {
const iso = entry.created.toISOString() // appears like a globalThis.Date
}The above would be the natural way to express arbitrary structures using the TB V1 type system .... and much of the ground work has been setup to move the library in this direction. I guess the main issue with Base is it requires bolting on all this intrinsic functionality (Check, Clone, Clean, Default, etc) just to be able to compensate for the fact it isn't a schematic,. My general thinking is that the additional complexity to support Extends / Evaluate probably indicates the Base type is probably the wrong fit. What are your thoughts on this latter example? Again, sorry for the delay (busy end to the year), but still very keen to discuss this further. I'd like to get some thoughts on this latter approach as it would be technically feasible to validate for Date (or other) instances using structural checks (while also letting types naturally evaluate under the TBV1 type system) It's a bit of tangent from Base (we can return to this), but would be keen to hear your thoughts on the above all the same. |
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hi, just a quick additional update here. I have just pushed a small update on 1.0.64 to enables the following. const Uint8ArrayType = Type.Object({
BYTES_PER_ELEMENT: Type.Literal(1),
byteLength: Type.Number(),
byteOffset: Type.Number(),
// ... additional
})
const DateType = Type.Object({
getTime: Type.Function([], Type.Number()),
toDateString: Type.Function([], Type.String()),
toISOString: Type.Function([], Type.String()),
// ... additional
})
console.log('Uint8Array', Value.Check(Uint8ArrayType, new Uint8Array())) // true
console.log('Date', Value.Check(DateType, new Date())) // true.. and for TId import { ObjectId } from 'npm:mongodb' // ???
const TId = Type.Object({
_bsontype: Type.Literal('ObjectId'),
getTimestamp: Type.Function([], Type.Unknown()),
toHexString: Type.Function([], Type.Unknown()),
// ... additional
})
console.log('TId', Value.Check(TId, new ObjectId())) // true... In the case of Date, it would be possible wrap the schema in Unsafe to infer as a import Type from 'typebox'
const DateType = Type.Unsafe<globalThis.Date>(Type.Object({
getTime: Type.Function([], Type.Number()),
toDateString: Type.Function([], Type.String()),
toISOString: Type.Function([], Type.String()),
// ... additional
}))
type T = Type.Static<typeof DateType> // type T = DateAgain, open to thoughts here. The function checks are not perfectly ideal as it's not possible to derive parameter and return type information from JavaScript functions (the best TB can do tell a function exists at a instance This is worth some consideration as an alternative to Base. |
Beta Was this translation helpful? Give feedback.
-
|
One more reference link for consideration Reference Link // ------------------------------------------------------------------------
// type derived from lib.d.ts
// ------------------------------------------------------------------------
export const DateType = () => Type.Object({
[Symbol.toPrimitive]: Type.Any(),
/** Returns a string representation of a date. The format of the string depends on the locale. */
toString: Type.Function([], Type.String()),
/** Returns a date as a string value. */
toDateString: Type.Function([], Type.String()),
/** Returns a time as a string value. */
toTimeString: Type.Function([], Type.String()),
/** Returns a value as a string value appropriate to the host environment's current locale. */
toLocaleString: Type.Function([], Type.String()),
/** Returns a date as a string value appropriate to the host environment's current locale. */
toLocaleDateString: Type.Function([], Type.String()),
/** Returns a time as a string value appropriate to the host environment's current locale. */
toLocaleTimeString: Type.Function([], Type.String()),
/** Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC. */
valueOf: Type.Function([], Type.Number()),
/** Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC. */
getTime: Type.Function([], Type.Number()),
/** Gets the year, using local time. */
getFullYear: Type.Function([], Type.Number()),
/** Gets the year using Universal Coordinated Time (UTC). */
getUTCFullYear: Type.Function([], Type.Number()),
/** Gets the month, using local time. */
getMonth: Type.Function([], Type.Number()),
/** Gets the month of a Date object using Universal Coordinated Time (UTC). */
getUTCMonth: Type.Function([], Type.Number()),
/** Gets the day-of-the-month, using local time. */
getDate: Type.Function([], Type.Number()),
// ...
}) |
Beta Was this translation helpful? Give feedback.
-
|
@sinclairzx81, sorry for late response as well, things are busy here. I was hoping to not express
|
Beta Was this translation helpful? Give feedback.
-
|
Hello @sinclairzx81! I hope the 2026 brings you lots of joy and success :) const schemaRecordId = Type.Object({
table: Type.String(),
id: Type.String(),
});Of course, it's possible to use |
Beta Was this translation helpful? Give feedback.
-
|
Hello @sinclairzx81! I hope things go well. Just noticed the latest release deprecating
The code with import Type, { StaticDecode } from 'typebox'
const TId = Type.Refine(Type.Unsafe<string>({}), value => typeof value === 'string')
const schemaNonEvaluated = Type.Intersect([
TId,
TId,
]);
type TNonEvaluated = StaticDecode<typeof schemaNonEvaluated>;
/**
* const schemaNonEvaluated: Type.TIntersect<[TId, TId]>;
* type TNonEvaluated = string;
*/
const schemaEvaluated = Type.Evaluate(schemaNonEvaluated);
type TEvaluated = StaticDecode<typeof schemaEvaluated>;
/**
* const schemaEvaluated: Type.TNever;
* type TEvaluated = never;
*
* instead of
*
* const schemaEvaluated: Type.TRefine<Type.TUnsafe<string>>;
* type TEvaluated = string;
*/ |
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hi, sorry for the delay,
Yeah i know. I finally gave up and decided to deprecate Base (basically for the reasons above, as I think the type was a mistake) and reconciling operations around these class instances was just proving too complex when the entire TB compositor based on remapping structured objects (i.e. schemas). It doesn't fix this issue, rather it reduces the problem down to the Refine(...) function which itself produces a structured object compatible with the compositor. const T = Type.Refine({}, value => value instanceof Date)
// represented as keyword extension (to be replaced with 'x-refine' in later versions)
const T = { '~refine': [[(value => value instanceof Date, '...')]] }Note that while it's still not possible to reflect into Refine(...) to learn what it is refining for, the const A = Type.Refine({}, value => value instanceof Date)
const B = Type.Refine({}, value => value instanceof Date)
const C = Type.Evaluate(Type.Intersect([A, B]))
// ... evaluated Refine === stacked refinements (AND operations)
const C = {
'~refine': [
[value => value instanceof Date, '...'],
[value => value instanceof Date, '...']
]
}
// ... and where C is representative of a
type C = Date & Date // ... evaluated as DateBased on the above idea, it should be possible to prototype the Refine / Evaluate on a TB fork (so will be doing that over the next while when I have the time (community help is welcome too!)), but deprecating Base now just narrows the problem down to the parts of the current API where I think a solution is the most promising (Unsafe/Refine) Still working on this though! Happy to keep the discussion going! |
Beta Was this translation helpful? Give feedback.
-
|
@sinclairzx81 Hi, thanks for keeping the amazing project up! I love your refinements stacking idea! Sounds amazing. I got a couple of ideas you might find interesting. Since we can infer guards, we can store that info in type as safely-inferred: import Type from 'typebox'
const safeType: unique symbol = Symbol();
type TypeTSafe<T> = { [safeType]: T };
function TypeTSafe<T>() {
return {} as TypeTSafe<T>;
}
function TypeTGuarded<V>(cb: (v: unknown) => v is V) {
return Type.Refine(TypeTSafe<V>(), cb);
}
const TSafeDate = TypeTGuarded(value => value instanceof Date);
/**
* const TSafeDate: Type.TRefine<TypeTSafe<Date>>
*/It is also possible to make class instance types, that take a class as an argument and store its info: import Type from 'typebox'
function TypeTClass<T>(c: new (...args: unknown[]) => T) {
return Type.Refine({ ['~class']: c }, v => v instanceof c);
}
const TSafeDate = TypeTClass(Date);
/**
* const TSafeDate: Type.TRefine<{
* "~class": new (...args: unknown[]) => Date;
* }>
*/Either way, it may improve the DX for using classes with TypeBox, while also allow avoiding the discomfort of using p.s. import Type, { StaticDecode } from 'typebox'
const TDate = Type.Unsafe<Date>({});
const schemaNonEvaluated = Type.Intersect([
TDate,
TDate,
]);
type TNonEvaluated = StaticDecode<typeof schemaNonEvaluated>;
/**
* const schemaNonEvaluated: Type.TIntersect<[Type.TUnsafe<Date>, Type.TUnsafe<Date>]>;
* type TNonEvaluated = Date;
*/
const schemaEvaluated = Type.Evaluate(schemaNonEvaluated);
type TEvaluated = StaticDecode<typeof schemaEvaluated>;
/**
* const schemaEvaluated: Type.TNever;
* type TEvaluated = never;
*
* instead of
*
* const schemaEvaluated: Type.TRefine<Type.TUnsafe<Date>>;
* type TEvaluated = Date;
*/ |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hi! Sorry again for the delay, still thinking about this issue (it's a difficult design problem!) Unsafe/Refine
Yeah. I think the Refine/Unsafe pairing do strike a fairly good balance. I note that historically, Unsafe has proven to be a very useful mechanism to enable TypeBox to integrate with legacy OpenAPI schematics, the most notorious schema being Nullable. // this has proven to be exceptionally useful
const Nullable = <Type extends Type.TSchema>(type: Type) =>
Type.Unsafe<Type.Static<Type> | null>({ ...type, nullable: true })... but for Refine, there are actually still a few challenges regarding generality (specifically for direct schema augmentation - as would be needed for Nullable) import Value from 'typebox/value'
import Type from 'typebox'
// nullable + validation refinement
const Nullable = <Type extends Type.TSchema>(type: Type) =>
Type.Refine(Type.Unsafe<Type.Static<Type> | null>({ ...type, nullable: true }),
value => Value.Check(type, value) || value === null
)
const T = Nullable(Type.String()) // { type: 'string', '~refine': () => { ... } }
Value.Parse(T, 'hello') // ok!
Value.Parse(T, null) // error: "must be string"
//
// This is because TB observes keywords as
// logical AND for `type` and `~refine` which
// technically satisfies a "refinement", but
// breaks for augmented schematics.
//
// { type: 'string' } AND { '~refine': () => { .. } } <-- need OR check!As of 1.x Refine is the only mechanism implemented (And), but have been considering whether a RefineOr/RefineAnd API would make sense. See the links below to see how TypeBox handles the current Refine checks ... and where a RefineOr would wrap the each logical expression in a Build: https://github.com/sinclairzx81/typebox/blob/main/src/schema/engine/schema.ts#L231 In all, I do think Refine is the right path, and a logical AND/OR version of it might enable TypeBox apply some static/type-level reasoning to the interior type ... it is worth exploring. Unsafe/Evaluate
Yeah, this is expected. It is the same problem as Base where there is no way for TypeBox to make runtime structural call to determine if 2 Unsafe types are structurally equivalent. const A = Type.Unsafe<Date>({})
const B = Type.Unsafe<Uint8Array>({})
const T = Type.Intersect([A, B])
// Runtime
const R = Type.Evaluate(T) // R = { ...A, ...B } <-- runtime sees thisThis said, I would actually be a bit more open to exploring adding Unsafe to TB's Extends/Evaluate logic using the following rules. const A = Type.Unsafe<Date>({})
const B = Type.Unsafe<Uint8Array>({})
const T = Type.Intersect([A, B])
// Static
const R = Type.Evaluate(T) // C: TUnsafe<Date & UInt8Array> ? .. but it would need some thinking about ... So really difficult design problems! As with the Base type, there really isn't a easy way to incorporate user defined schema and check logic in Evaluate as the type system is deriving everything from strict keyword semantics (JSON Schema), but I am still searching.... I think Refine works well for validation cases now, but for integration into Evaluate, that's going to take some thinking. Let's keep this conversation going! :) |
Beta Was this translation helpful? Give feedback.
-
|
I see, thank you! But do we really need a structural comparison? If we have two unsafe, let's say, Date or UInt8Array types, we only care that they are the two Date or UInt8Array types, right? So this is what we should be getting: const A1 = Type.Unsafe<Date>({});
const A2 = Type.Unsafe<Date>({});
const B1 = Type.Unsafe<Uint8Array>({});
const B2 = Type.Unsafe<Uint8Array>({});
const T1 = Type.Intersect([A1, A2]);
const T2 = Type.Intersect([B1, B2]);
const T = Type.Intersect([A1, B1]);
// Static
const R1 = Type.Evaluate(T1); // R1: TUnsafe<Date>
const R2 = Type.Evaluate(T2); // R2: TUnsafe<UInt8Array>
const R = Type.Evaluate(T); // R: TUnsafe<Date & UInt8Array>I think it must be safe to assume that TUnsafe<A> & TUnsafe<B> === TUnsafe<A & B>At the very least, when |
Beta Was this translation helpful? Give feedback.
-
Yeah, unfortunately it's needed. Keep in mind that, TypeBox is attempting to model TypeScript's type system at runtime, and maintains a fairly large submodule written to symmetrically answer TS questions such as "does https://github.com/sinclairzx81/typebox/tree/main/src/type/extends Evaluate | ExtendsThese modules all perform structural comparisons against two known (Left, Right) JSON Schemas and get used throughout TypeBox to mirror TS evaluation behaviors. For example, Evaluate uses Extends in the following way ... https://github.com/sinclairzx81/typebox/blob/mapped/src/type/engine/evaluate/compare.ts#L33-L73 ... which enables Canonical Type Normalization of Logical type expressions (TS) import Type from 'typebox'
export const { Logical, Canonical } = Type.Script(`
type Logical = { x: number } & ({ y: number }| { z: number })
type Canonical = Evaluate<Logical>
`)
// const Logical: Type.TIntersect<[Type.TObject<{
// x: Type.TNumber;
// }>, Type.TUnion<[Type.TObject<{
// y: Type.TNumber;
// }>, Type.TObject<{
// z: Type.TNumber;
// }>]>]>
// const Canonical: Type.TUnion<[Type.TObject<{
// x: Type.TNumber;
// y: Type.TNumber;
// }>, Type.TObject<{
// x: Type.TNumber;
// z: Type.TNumber;
// }>]>
type Logical = Type.Static<typeof Logical> // type Logical = {
// x: number;
// } & ({
// y: number;
// } | {
// z: number;
// })
type Canonical = Type.Static<typeof Canonical> // type Canonical = {
// x: number;
// y: number;
// } | {
// x: number;
// z: number;
// }The main issue with Unsafe is that TB cannot structurally compare the schematics (because it doesn't know what they are), and where a single schematic "could" represent two distinct types (Date, Uint8Array). There are only really two possible types Unsafe could be interpreted as:
Neither interpretation is ideal :( NominalIt's really a difficult problem to solve. One approach I have been mulling is to maybe investigate some kind of Nominal typing support that might allow TypeBox to test if a type is Equal and Not-Equal (but not Extends!) const A = Type.Nominal<Uint8Array>({ type: 'Uint8Array' }) // Must implement `type` such that
const B = Type.Nominal<Date>({ type: 'Date' }) // Extends can compare for TNominalThat would enable custom types to participate in Extends checks (and be compat with Refine) // Extends checks guarded on Nominal
const C = Type.Refine(Type.Nominal<Date>({ type: 'Date' }), value => value instanceof Date)Where Nominal would be a more constrained type of Unsafe. Thoughts? |
Beta Was this translation helpful? Give feedback.
-
|
Hmm, this sounds really nice! It will even make it possible to have helpers for generic cases. I love it! |
Beta Was this translation helpful? Give feedback.
-
|
@orimay Hi!, Hey, I am just doing some issue maintenance, and I may move this issue into discussions as the Base type is set for deprecation (which is what the original issue relates to), but I am keen to continue discussion on a potential Nominal(T). I will swap things over and rename the heading to "Nominal Type Discussion", and we can continue things from there. Will be looking at the Nominal type again once TB completes a round of Json Schema spec compliance work. |
Beta Was this translation helpful? Give feedback.
-
|
Sounds good! This feature is really exciting, if it will solve the evaluation/intersection issues |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
When I try to intersect a type extended from base and then evaluate it, it turns into
neverShowcase: https://play.vuejs.org/#eNqNlMty2jAUhl9Fo00IQ+1Fu6IOnaZlQRe003gXZaHYB+PEljTSMU6G4d1zJAMxt0w2HnSu3y9+e81/GhOtGuBjnrjMlgaZA2wMq6QqbgRHJ/hEqLI22iJbs/TVwIghPdkdSiyz35DpHNiGLayu2ZXPPOqXq+9CCZVV0jmWznIGLwgqp9+Ujm6lg8ShLVUxYetNqNTK0eZsCbWcazVdyaqRCDm76VpmCsE6yHBwLxRjClo/dnA9Oj49XNPmgJcezenjJr5CL84snATweDikwUPGLnGNO6x0z5XcE8HIYzz4Eb73LEUnO1TEx8JPVO8Cg1OAd5mf0/gZgSfq5rACe6Cmv0z1st2zpFkgc6YX/eilFbP84ujDW0rizpnkQzog1KaiOjolce/AR7xz6ZdamujJaUWeXnt/iG2CrDxmIeJjW6f6mOBLROPGcdy2bdQo81xEma7jbcmPWudNBYL7VrLrhlahI1mLsjhaRF2mrMD+NViS7IOFsqp0+yfE0DYQrBt6lpA9n4k/uS3cPwsO7IoA9jmUtgDs0tO7Ob1eveQO94Pkf3C6ajxjV3bbqJywe3WBdhbujf6K1E39G+x2ojxouI1QLzh9QX59IP0d92v0bX+LmzeR+4iI
Beta Was this translation helpful? Give feedback.
All reactions