Typescript: [tsc] Regression causing 'Maximum call stack size exceeded'

Created on 17 Sep 2019  路  12Comments  路  Source: microsoft/TypeScript

Hopefully this one can get fixed quickly since it is a pretty major regression. I spent a lot of time figuring out exactly which version range is causing the failure as well as trying to figure out a minimal test case.

TypeScript Version:

Failure at version: 3.6.0-dev.20190807 and above.
Everything works: 3.6.0-dev.20190806 and lower.

Code

tsc-fail.zip

To run:

yarn
./node_modules/.bin/tsc

Expected behavior:

No error.

Actual behavior:

tsc-fail/node_modules/typescript/lib/tsc.js:75299
                throw e;
                ^

RangeError: Maximum call stack size exceeded
    at Object.getObjectFlags (tsc-fail/node_modules/typescript/lib/tsc.js:10227:28)
    at isExcessPropertyCheckTarget (tsc-fail/node_modules/typescript/lib/tsc.js:42328:51)
    at Object.every (tsc-fail/node_modules/typescript/lib/tsc.js:295:22)
    at isExcessPropertyCheckTarget (tsc-fail/node_modules/typescript/lib/tsc.js:42331:44)
    at isKnownProperty (tsc-fail/node_modules/typescript/lib/tsc.js:42317:52)
    at hasCommonProperties (tsc-fail/node_modules/typescript/lib/tsc.js:37523:21)
    at isRelatedTo (tsc-fail/node_modules/typescript/lib/tsc.js:36276:58)
    at typeRelatedToSomeType (tsc-fail/node_modules/typescript/lib/tsc.js:36458:35)
    at isRelatedTo (tsc-fail/node_modules/typescript/lib/tsc.js:36300:34)
    at isPropertySymbolTypeRelated (tsc-fail/node_modules/typescript/lib/tsc.js:37137:28)
Bug Crash

Most helpful comment

Oh, the original reproduction was also done using objection typings :smile:

All 12 comments

Maybe related: #32582

Running with 3.6.3 the error has changed a bit...

RangeError: Maximum call stack size exceeded
    at recursiveTypeRelatedTo (/node_modules/typescript/lib/tsc.js:36800:44)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36477:38)
    at typeRelatedToSomeType (/node_modules/typescript/lib/tsc.js:36614:35)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36456:34)
    at eachTypeRelatedToType (/node_modules/typescript/lib/tsc.js:36736:35)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36452:25)
    at isPropertySymbolTypeRelated (/node_modules/typescript/lib/tsc.js:37293:28)
    at propertyRelatedTo (/node_modules/typescript/lib/tsc.js:37333:31)
    at propertiesRelatedTo (/node_modules/typescript/lib/tsc.js:37434:43)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36468:34)

Version 3.7.0-dev.20190917

/node_modules/typescript/lib/tsc.js:75802
                throw e;
                ^

RangeError: Maximum call stack size exceeded
    at isUnconstrainedTypeParameter (/node_modules/typescript/lib/tsc.js:37850:46)
    at /node_modules/typescript/lib/tsc.js:37854:105
    at Object.some (/node_modules/typescript/lib/tsc.js:673:25)
    at isTypeReferenceWithGenericArguments (/node_modules/typescript/lib/tsc.js:37854:58)
    at getRelationKey (/node_modules/typescript/lib/tsc.js:37884:17)
    at recursiveTypeRelatedTo (/node_modules/typescript/lib/tsc.js:36895:26)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36568:38)
    at eachTypeRelatedToType (/node_modules/typescript/lib/tsc.js:36827:35)
    at isRelatedTo (/node_modules/typescript/lib/tsc.js:36543:25)
    at isPropertySymbolTypeRelated (/node_modules/typescript/lib/tsc.js:37393:28)    

This also happens with objection.js typings. I don't know if this helps, but you can reproduce it like this:

git clone [email protected]:Vincit/objection.js.git
git checkout typescript-issue-33460
npm install
npm install [email protected]
npm run test-typings

Using the package.json version 3.5.3 works

npm install [email protected]
npm run test-typings

I was able to find out that this monstrosity of a type is the culprit:

Oh, the original reproduction was also done using objection typings :smile:

@koskimas I'm also sorry to say that I've now moved on from Objection to MikroORM. =(

I just found out that changing this

  type GraphParameters = {
    '#dbRef'?: MaybeCompositeId;
    '#ref'?: string;
    '#id'?: string;
  };

  type PartialModelGraph<T> = {
    [K in NonFunctionPropertyNames<T>]?: Exclude<T[K], undefined> extends Model
      ? PartialModelGraph<Exclude<T[K], undefined>>
      : Exclude<T[K], undefined> extends Array<infer I>
      ? (I extends Model ? PartialModelGraph<I>[] : (T[K] | NonPrimitiveValue))
      : (T[K] | NonPrimitiveValue);
  } &
    GraphParameters;

into this

  type GraphParameters = {
    '#dbRef'?: MaybeCompositeId;
    '#ref'?: string;
    '#id'?: string;
  };

  type PartialModelGraph<M, T = M & GraphParameters> = {
    [K in NonFunctionPropertyNames<T>]?: Exclude<T[K], undefined> extends Model
      ? PartialModelGraph<Exclude<T[K], undefined>>
      : Exclude<T[K], undefined> extends Array<infer I>
      ? (I extends Model ? PartialModelGraph<I>[] : (T[K] | NonPrimitiveValue))
      : (T[K] | NonPrimitiveValue);
  }

fixes the problem.

Also note that @lookfirst's reproduction uses objection 1.6.9 while the above types and my repro is with objection 2.0 typings which have been completely rewritten from scratch. So this fix only applies to my repro.

I just ran into the same problem on v3.7.2

RangeError: Maximum call stack size exceeded
    at isRelatedTo (node_modules\typescript\lib\tsc.js:1:1)
    at typeRelatedToSomeType (node_modules\typescript\lib\tsc.js:38604:35)
    at isRelatedTo (node_modules\typescript\lib\tsc.js:38438:34)
    at eachTypeRelatedToType (node_modules\typescript\lib\tsc.js:38726:35)
    at isRelatedTo (node_modules\typescript\lib\tsc.js:38434:25)
    at isPropertySymbolTypeRelated (node_modules\typescript\lib\tsc.js:39293:28)
    at propertyRelatedTo (node_modules\typescript\lib\tsc.js:39333:31)
    at propertiesRelatedTo (node_modules\typescript\lib\tsc.js:39437:43)
    at isRelatedTo (node_modules\typescript\lib\tsc.js:38450:34)
    at typeRelatedToSomeType (node_modules\typescript\lib\tsc.js:38604:35)

The problem appeared when using "morphism" together with "sequelize" and I managed to reproduce it with this:
(sorry, it's still a bit lengthy)

function morphism<TSchema = Schema<DestinationFromSchema<Schema>, SourceFromSchema<Schema>>, Source extends SourceFromSchema<TSchema> = SourceFromSchema<TSchema>>(schema: TSchema, data: Source[]): DestinationFromSchema<TSchema>[] {
  throw new Error();
}

type SourceFromSchema<T> = T extends Schema<unknown, infer U> ? U : never;
type DestinationFromSchema<T> = T extends Schema<infer U> ? U : never;

declare const SCHEMA_OPTIONS_SYMBOL: unique symbol;

type Schema<Target = any, Source = any> = {
  [destinationProperty in keyof Target]?: Schema<Target[destinationProperty], Source>;
} & {
  [SCHEMA_OPTIONS_SYMBOL]?: any;
};

export class Sequelize {
  public Sequelize!: typeof Sequelize;
}

interface MyModel extends Sequelize {
  vehicleId: string;
}

export interface MyModelDTO {
  id: string;
}

export const MyModelDTOVehicleModel: Schema<MyModel, MyModelDTO> = {
  vehicleId: 'id',
};

const output: MyModel[] = morphism(MyModelDTOVehicleModel, [{
  id: 'ID',
}]);

As mentioned before it works in version 3.5.3.

Just use MikroORM v3, seriously.

Spent this morning looking into this:

  • Still happens with master
  • Probably is due to https://github.com/microsoft/TypeScript/pull/32582 (as mentioned above)
  • getUnmatchedProperties doesn't seem to show in any stack traces (likely because it's a generator func which is transpile out) but it's where a lot of the looping goes through

The loop seems to roughly be (based on getUnmatchedProperties):

  • R -> P

    • hits the Intersection|IncludesNonWideningType with R -> P as

      '{ '#id'?: string; '#ref'?: never; '#dbRef'?: never }' and

      { [P in NonFunctionPropertyNames<T>]?: DeepPartialGraph<T[P]> } in the target

  • R ('Object') -> R ('Intersection|IncludesNonWideningType')

    • P (Object) -> P (Intersection|IncludesNonWideningType)

    • R -> P again

I've not been able to replicate this in a baseline yet


Current baseline WIP

// R -> Model
//

// @target: ES2019
// @strict

export interface Constructor {
new (...args: any[]): M;
}

export interface QueryBuilderYieldingOne extends QueryBuilder {}

export interface QueryBuilderYieldingOneOrNone extends QueryBuilder {}

export interface QueryBuilderYieldingCount extends QueryBuilderBase,
Executable {
throwIfNotFound(): this;
}

type Value =
| string
| number
| boolean
| Date
| string[]
| number[]
| boolean[]
| Date[]
| null
| Raw
| Literal;

export interface Page {
total: number;
results: QM[];
}

interface BluebirdMapper {
(item: T, index: number): Result;
}

interface NodeStyleCallback {
(err: any, result?: any): void;
}

interface InsertGraph {
(modelsOrObjects?: DeepPartialGraph[], options?: InsertGraphOptions): QueryBuilder (modelOrObject?: DeepPartialGraph, options?: InsertGraphOptions): QueryBuilder;
(): this;
}

interface QueryBuilderBase extends QueryInterface {
modify(func: (builder: this) => void): this;
modify(namedFilter: string): this;

applyFilter(...namedFilters: string[]): this;

// findById(id: Id): QueryBuilderYieldingOneOrNone;
// findById(idOrIds: IdOrIds): this;
// findByIds(ids: Id[] | Id[][]): this;
// /** findOne is shorthand for .where(...whereArgs).first() */
// findOne: FindOne;

// insert: Insert;
// insertAndFetch(modelOrObject: Partial): QueryBuilder;
// insertAndFetch(modelsOrObjects?: Partial[]): QueryBuilder

insertGraph: InsertGraph;
// insertGraphAndFetch: InsertGraphAndFetch;

// /**
// * insertWithRelated is an alias for insertGraph.
// */
// insertWithRelated: InsertGraph;
// insertWithRelatedAndFetch: InsertGraphAndFetch;

// /**
// * @return a Promise of the number of updated rows
// */
// update(modelOrObject: PartialUpdate): QueryBuilderYieldingCount;
// updateAndFetch(modelOrObject: PartialUpdate): QueryBuilder;
// updateAndFetchById(id: Id, modelOrObject: PartialUpdate): QueryBuilder;

// /**
// * @return a Promise of the number of patched rows
// */
// patch(modelOrObject: PartialUpdate): QueryBuilderYieldingCount;
// patchAndFetchById(idOrIds: IdOrIds, modelOrObject: PartialUpdate): QueryBuilder;
// patchAndFetch(modelOrObject: PartialUpdate): QueryBuilder;

// upsertGraph: UpsertGraph;
// upsertGraphAndFetch: UpsertGraphAndFetch;

// /**
// * @return a Promise of the number of deleted rows
// */
// deleteById(idOrIds: IdOrIds): QueryBuilderYieldingCount;

// relate(ids: IdOrIds | Partial | Partial[]): this;
// unrelate(): this;

// forUpdate(): this;
// forShare(): this;

// // TODO: fromJS does not exist in current knex documentation: http://knexjs.org/#Builder-fromJS
// withSchema(schemaName: string): this;

// joinRelation: JoinRelation;
// innerJoinRelation: JoinRelation;
// outerJoinRelation: JoinRelation;
// leftJoinRelation: JoinRelation;
// leftOuterJoinRelation: JoinRelation;
// rightJoinRelation: JoinRelation;
// rightOuterJoinRelation: JoinRelation;
// fullOuterJoinRelation: JoinRelation;

// // TODO: avgDistinct does not exist in current knex documentation: http://knexjs.org/#Builder-fromJS
// // TODO: modify does not exist in current knex documentation: http://knexjs.org/#Builder-modify

// // TODO: the return value of this method matches the knex typescript and documentation.
// // The Objection documentation incorrectly states this returns a QueryBuilder.
// columnInfo(column?: string): Promise;

// whereComposite(column: ColumnRef, value: Value | QueryBuilder // whereComposite(column: ColumnRef[], value: Value[] | QueryBuilder // whereComposite(
// column: ColumnRef,
// operator: string,
// value: Value | QueryBuilder // ): this;
// whereComposite(
// column: ColumnRef[],
// operator: string,
// value: Value[] | QueryBuilder // ): this;
// whereInComposite(column: ColumnRef | ColumnRef[], values: Value[] | QueryBuilder

// whereJsonSupersetOf: WhereJson;
// orWhereJsonSupersetOf: WhereJson;

// whereJsonNotSupersetOf: WhereJson;
// orWhereJsonNotSupersetOf: WhereJson;

// whereJsonSubsetOf: WhereJson;
// orWhereJsonSubsetOf: WhereJson;

// whereJsonNotSubsetOf: WhereJson;
// orWhereJsonNotSubsetOf: WhereJson;

// whereJsonIsArray: WhereFieldExpression;
// orWhereJsonIsArray: WhereFieldExpression;

// whereJsonNotArray: WhereFieldExpression;
// orWhereJsonNotArray: WhereFieldExpression;

// whereJsonIsObject: WhereFieldExpression;
// orWhereJsonIsObject: WhereFieldExpression;

// whereJsonNotObject: WhereFieldExpression;
// orWhereJsonNotObject: WhereFieldExpression;

// whereJsonHasAny: WhereJsonExpression;
// orWhereJsonHasAny: WhereJsonExpression;

// whereJsonHasAll: WhereJsonExpression;
// orWhereJsonHasAll: WhereJsonExpression;

// Non-query methods:

context(queryContext: object): this;
context(): QueryContext;
mergeContext(queryContext: object): this;

reject(reason: any): this;
resolve(value: any): this;

isExecutable(): boolean;
isFind(): boolean;
isInsert(): boolean;
isUpdate(): boolean;
isDelete(): boolean;
isRelate(): boolean;
isUnrelate(): boolean;
hasWheres(): boolean;
hasSelects(): boolean;
hasEager(): boolean;

runBefore(fn: (result: any, builder: QueryBuilder) => any): this;
runAfter(fn: (result: any, builder: QueryBuilder) => any): this;
onBuild(fn: (builder: this) => void): this;
//onBuildKnex(fn: (knexBuilder: knex.QueryBuilder, builder: this) => void): this;
onError(fn: (error: Error, builder: this) => any): this;

//eagerAlgorithm(algo: EagerAlgorithm): this;
// eagerOptions(opts: EagerOptions): this;

// eager(relationExpression: RelationExpression, filters?: FilterExpression): this;
// mergeEager(relationExpression: RelationExpression, filters?: FilterExpression): this;

// joinEager(relationExpression: RelationExpression, filters?: FilterExpression): this;
// mergeJoinEager(relationExpression: RelationExpression, filters?: FilterExpression): this;

// naiveEager(relationExpression: RelationExpression, filters?: FilterExpression): this;
// mergeNaiveEager(relationExpression: RelationExpression, filters?: FilterExpression): this;

// allowEager: RelationExpressionMethod;
// modifyEager: ModifyEager;
// filterEager: ModifyEager;

// allowInsert: RelationExpressionMethod;
// allowUpsert: RelationExpressionMethod;

modelClass(): typeof Model;

toString(): string;

toSql(): string;

skipUndefined(): this;

transacting(transaction: Transaction): this;

clone(): this;

// We get then and catch by extending Promise

map(mapper: BluebirdMapper): Promise return(returnValue: V): Promise;
bind(context: any): Promise;
reflect(): Promise;

asCallback(callback: NodeStyleCallback): Promise;

nodeify(callback: NodeStyleCallback): Promise;

resultSize(): Promise;

page(page: number, pageSize: number): QueryBuilder>;
// range(): QueryBuilder>;
range(start: number, end: number): QueryBuilder>;
pluck(propertyName: string): this;
// first(): QueryBuilderYieldingOneOrNone;

// alias(alias: string): this;
// aliasFor(modelClassOrTableName: string | ModelClass, alias:string): this;
// tableRefFor(modelClass: ModelClass): string;
// tableNameFor(modelClass: ModelClass): string;

//traverse(modelClass: typeof Model, traverser: TraverserFunction): this;

pick(modelClass: typeof Model, properties: string[]): this;
pick(properties: string[]): this;

omit(modelClass: typeof Model, properties: string[]): this;
omit(properties: string[]): this;

returning(columns: string | string[]): QueryBuilder;

// timeout(ms: number, options?: TimeoutOptions): this;
}

export interface Executable extends Promise {
execute(): Promise;
}

export interface QueryBuilder
extends QueryBuilderBase,
Executable {
throwIfNotFound(): QueryBuilder;
castTo(model: T): QueryBuilder[]>;
}

interface Transaction {
savepoint(transactionScope: (trx: Transaction) => any): Promise;
commit(value?: any): Promise;
rollback(error?: Error): Promise;
}

interface QueryInterface {
select: Select;
as: As;
columns: Select;
column: Select;
from: Table;
into: Table;
table: Table;
distinct: Distinct;

// Joins
join: Join;
joinRaw: JoinRaw;
innerJoin: Join;
leftJoin: Join;
leftOuterJoin: Join;
rightJoin: Join;
rightOuterJoin: Join;
outerJoin: Join;
fullOuterJoin: Join;
crossJoin: Join;

// Withs
with: With;
withRaw: WithRaw;
withWrapped: WithWrapped;

// Wheres
where: Where;
andWhere: Where;
orWhere: Where;
whereNot: Where;
andWhereNot: Where;
orWhereNot: Where;
whereRaw: WhereRaw;
orWhereRaw: WhereRaw;
andWhereRaw: WhereRaw;
whereWrapped: WhereWrapped;
havingWrapped: WhereWrapped;
whereExists: WhereExists;
orWhereExists: WhereExists;
whereNotExists: WhereExists;
orWhereNotExists: WhereExists;
whereIn: WhereIn;
orWhereIn: WhereIn;
whereNotIn: WhereIn;
orWhereNotIn: WhereIn;
whereNull: WhereNull;
orWhereNull: WhereNull;
whereNotNull: WhereNull;
orWhereNotNull: WhereNull;
whereBetween: WhereBetween;
orWhereBetween: WhereBetween;
andWhereBetween: WhereBetween;
whereNotBetween: WhereBetween;
orWhereNotBetween: WhereBetween;
andWhereNotBetween: WhereBetween;
whereColumn: Where;
andWhereColumn: Where;
orWhereColumn: Where;
whereNotColumn: Where;
andWhereNotColumn: Where;
orWhereNotColumn: Where;

// Group by
groupBy: GroupBy;
groupByRaw: RawMethod;

// Order by
orderBy: OrderBy;
orderByRaw: RawMethod;

// Union
union: SetOperations;
unionAll: SetOperations;
intersect: SetOperations;

// Having
having: Where;
andHaving: Where;
orHaving: Where;
havingRaw: WhereRaw;
orHavingRaw: WhereRaw;
havingIn: WhereIn;
orHavingIn: WhereIn;
havingNotIn: WhereIn;
orHavingNotIn: WhereIn;
havingNull: WhereNull;
orHavingNull: WhereNull;
havingNotNull: WhereNull;
orHavingNotNull: WhereNull;
havingExists: WhereExists;
orHavingExists: WhereExists;
havingNotExists: WhereExists;
orHavingNotExists: WhereExists;
havingBetween: WhereBetween;
orHavingBetween: WhereBetween;
havingNotBetween: WhereBetween;
orHavingNotBetween: WhereBetween;

// Clear
clearSelect(): this;
clearOrder(): this;
clearWhere(): this;

// Paging
offset(offset: number): this;
limit(limit: number): this;

// Aggregation
count(columnName?: string): this;
countDistinct(columnName?: string): this;
min(columnName: string): this;
max(columnName: string): this;
sum(columnName: string): this;
sumDistinct(columnName: string): this;
avg(columnName: string): this;
avgDistinct(columnName: string): this;
increment(columnName: string, amount?: number): this;
decrement(columnName: string, amount?: number): this;

debug(enabled?: boolean): this;
pluck(column: string): this;

//del(): QueryBuilderYieldingCount;
//delete(): QueryBuilderYieldingCount;
truncate(): this;

transacting(trx: Transaction): this;
connection(connection: any): this;

clone(): this;
}

interface Castable {
castText(): this;
castInt(): this;
castBigInt(): this;
castFloat(): this;
castDecimal(): this;
castReal(): this;
castBool(): this;
castJson(): this;
castArray(): this;
asArray(): this;
castType(sqlType: string): this;
castTo(sqlType: string): this;
as(alias: string): this;
}

export interface Literal extends Castable {}

export interface Reference extends Castable {}

type Raw = any;

interface As {
(alias: string): QueryBuilder;
}

interface Select extends ColumnNamesMethod {}

interface Table {
(tableName: TableName): QueryBuilder;
(callback: (this: QueryBuilder;
}

interface Distinct extends ColumnNamesMethod {}

interface Join {
(raw: Raw): QueryBuilder;
(tableName: TableName, clause: (this: any, join: any) => void): QueryBuilder;
(tableName: TableName, columns: { [key: string]: string | number | Raw | Reference }): QueryBuilder;
(tableName: TableName, raw: Raw): QueryBuilder;
(tableName: TableName, column1: ColumnRef, column2: ColumnRef): QueryBuilder;
(tableName: TableName, column1: ColumnRef, operator: string, column2: ColumnRef): QueryBuilder;
(queryBuilder: QueryBuilder): QueryBuilder;
}

type ColumnRef = string | Raw | Reference | QueryBuilder type TableName = string | Raw | Reference | QueryBuilder

interface JoinRaw {
(sql: string, bindings?: any): QueryBuilder;
}

interface With extends WithRaw, WithWrapped {}

interface WithRaw {
(alias: string, raw: Raw): QueryBuilder;
// join: knex.JoinClause;
(alias: string, sql: string, bindings?: any): QueryBuilder;
}

interface WithWrapped {
(alias: string, callback: (queryBuilder: QueryBuilder;
}

interface Where extends WhereRaw {
(callback: (this: QueryBuilder;
(object: object): QueryBuilder;
(column: keyof QM | ColumnRef, value: Value | Reference | QueryBuilder;
(column: keyof QM | ColumnRef, operator: string, value: Value | Reference | QueryBuilder QM,
RM,
RV

;
(
column: ColumnRef,
callback: (this: QueryBuilder ): QueryBuilder;
}

interface FindOne {
(condition: boolean): QueryBuilderYieldingOneOrNone;
(
callback: (this: QueryBuilder ): QueryBuilderYieldingOneOrNone;
(object: object): QueryBuilderYieldingOneOrNone;
(sql: string, ...bindings: any[]): QueryBuilderYieldingOneOrNone;
(sql: string, bindings: any): QueryBuilderYieldingOneOrNone;
(column: ColumnRef, value: Value | Reference | QueryBuilder;
(
column: ColumnRef,
operator: string,
value: Value | Reference | QueryBuilder ): QueryBuilderYieldingOneOrNone;
(
column: ColumnRef,
callback: (this: QueryBuilder ): QueryBuilderYieldingOneOrNone;
}

interface WhereRaw extends RawMethod {
(condition: boolean): QueryBuilder;
}

interface WhereWrapped {
(callback: (queryBuilder: QueryBuilder;
}

interface WhereNull {
(column: ColumnRef): QueryBuilder;
}

interface WhereIn {
(column: ColumnRef | ColumnRef[], values: Value[]): QueryBuilder;
(
column: ColumnRef | ColumnRef[],
callback: (this: QueryBuilder ): QueryBuilder;
(column: ColumnRef | ColumnRef[], query: QueryBuilder;
}

interface WhereBetween {
(column: ColumnRef, range: [Value, Value]): QueryBuilder;
}

interface WhereExists {
(callback: (this: QueryBuilder;
(query: QueryBuilder;
(raw: Raw): QueryBuilder;
}

interface GroupBy extends RawMethod, ColumnNamesMethod {}

interface OrderBy {
(column: ColumnRef, direction?: string): QueryBuilder;
}

interface SetOperations {
(
callback: (this: QueryBuilder wrap?: boolean
): QueryBuilder (
callbacks: ((this: QueryBuilder wrap?: boolean
): QueryBuilder (...callbacks: ((this: QueryBuilder QM,
QM[]

;
}

// commons

interface ColumnNamesMethod {
(...columnNames: ColumnRef[]): QueryBuilder;
(columnNames: ColumnRef[]): QueryBuilder;
}

interface RawMethod {
(sql: string, ...bindings: any[]): QueryBuilder;
(sql: string, bindings: any): QueryBuilder;
(raw: Raw): QueryBuilder;
}

// TS 2.5 doesn't support interfaces with static methods or fields, so
// this must be declared as a class:
declare class Model {
static tableName: string;
static jsonSchema: any;
static idColumn: string | string[];
static modelPaths: string[];
static relationMappings: any;
static jsonAttributes: string[];
static virtualAttributes: string[];
static uidProp: string;
static uidRefProp: string;
static dbRefProp: string;
static propRefRegex: RegExp;
static pickJsonSchemaProperties: boolean;
static defaultEagerAlgorithm?: any;
static defaultEagerOptions?: any;
// static QueryBuilder: typeof QueryBuilder;
static columnNameMappers: any;
static relatedFindQueryMutates: boolean;
static relatedInsertQueryMutates: boolean;
static modifiers: any;

// static raw: knex.RawBuilder;
// static fn: knex.FunctionHelper;

// static BelongsToOneRelation: Relation;
// static HasOneRelation: Relation;
// static HasManyRelation: Relation;
// static ManyToManyRelation: Relation;
// static HasOneThroughRelation: Relation;

// static JoinEagerAlgorithm: EagerAlgorithm;
// static WhereInEagerAlgorithm: EagerAlgorithm;
// static NaiveEagerAlgorithm: EagerAlgorithm;

// static getRelations(): { [key: string]: Relation };

static query(
this: Constructor,
trxOrKnex?: Transaction // | knex
): QueryBuilder;

// // This can only be used as a subquery so the result model type is irrelevant.
// static relatedQuery(relationName: string): QueryBuilder // static knex(knex?: knex): knex;
// static knexQuery(): knex.QueryBuilder;
// static bindKnex(this: M, knex: knex): M;
// static bindTransaction(this: M, transaction: Transaction): M;
// static createValidator(): Validator;
// static createValidationError(args: CreateValidationErrorArgs): Error;
// static createNotFoundError(): Error;

// // fromJson and fromDatabaseJson both return an instance of Model, not a Model class:
// static fromJson(this: Constructor, json: Pojo, opt?: ModelOptions): M;
// static fromDatabaseJson(this: Constructor, row: Pojo): M;

// static omitImpl(f: (obj: object, prop: string) => void): void;

// // loadRelated is overloaded to support both Model and Model[] variants:
// static loadRelated(
// this: Constructor,
// models: QM[],
// expression: RelationExpression,
// filters?: Filters,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder;

// static loadRelated(
// this: Constructor,
// model: QM,
// expression: RelationExpression,
// filters?: Filters,
// trxOrKnex?: Transaction | knex
// ): QueryBuilderYieldingOne;

// static traverse(
// filterConstructor: typeof Model,
// models: Model | Model[],
// traverser: TraverserFunction
// ): void;

// static traverse(models: Model | Model[], traverser: TraverserFunction): void;

// static tableMetadata(opt?: TableMetadataOptions): TableMetadata;
// static fetchTableMetadata(opt?: FetchTableMetadataOptions): Promise;
// // Implementation note: At least as of TypeScript 2.7, subclasses of
// // methods that return this are not compatible with their superclass.
// // For example, class Movie extends Model could not be passed as a
// // "Model" to a function, because the methods that return this return
// // Movie, and not Model. The foo<M>(this: M, ... is a workaround.

// $id(): any;
// $id(id: any): void;

// $beforeValidate(jsonSchema: JsonSchema, json: Pojo, opt: ModelOptions): JsonSchema;
// $validate(json: Pojo, opt: ModelOptions): Pojo; // may throw ValidationError if validation fails
// $afterValidate(json: Pojo, opt: ModelOptions): void; // may throw ValidationError if validation fails

// $toDatabaseJson(): object;
// $toJson(opt?: ToJsonOptions): object;
// toJSON(opt?: ToJsonOptions): object;
// $parseDatabaseJson(json: Pojo): Pojo;
// $formatDatabaseJson(json: Pojo): Pojo;
// $parseJson(json: Pojo, opt?: ModelOptions): Pojo;
// $formatJson(json: Pojo): Pojo;
// $setJson(this: T, json: Pojo, opt?: ModelOptions): T;
// $setDatabaseJson(this: M, json: Pojo): M;
// $setRelated(
// this: T,
// relation: String | Relation,
// related: RelatedM | RelatedM[] | null | undefined
// ): T;
// $appendRelated(
// this: T,
// relation: String | Relation,
// related: RelatedM | RelatedM[] | null | undefined
// ): T;

// $set(this: T, obj: Pojo): T;
// $omit(this: T, keys: string | string[] | Properties): T;
// $pick(this: T, keys: string | string[] | Properties): T;
// $clone(this: T, opt?: CloneOptions): T;

// $query(this: QM, trxOrKnex?: Transaction | knex): QueryBuilder;

// /**
// * If you add fields to your model, you get $relatedQuery typings for
// * free.
// *
// * Note that if you make any chained calls to the QueryBuilder,
// * though, you should apply a cast, which will make your code use not this
// * signatue, but the following signature.
// */
// $relatedQuery(
// relationName: K,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder;

// /**
// * Builds a query that only affects the models related to this instance
// * through a relation. Note that this signature requires a
// * type cast (like bob.$relatedQuery<Animal>('pets')).
// */
// $relatedQuery // relationName: keyof this | string,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder;

// $loadRelated(
// this: QM,
// expression: keyof this | RelationExpression,
// filters?: Filters,
// trxOrKnex?: Transaction | knex
// ): QueryBuilder;

// $traverse(traverser: TraverserFunction): void;
// $traverse(filterConstructor: this, traverser: TraverserFunction): void;

// $knex(): knex;
// $transaction(): knex;

// $beforeInsert(queryContext: QueryContext): Promise | void;
// $afterInsert(queryContext: QueryContext): Promise | void;
// $afterUpdate(opt: ModelOptions, queryContext: QueryContext): Promise | void;
// $beforeUpdate(opt: ModelOptions, queryContext: QueryContext): Promise | void;
// $afterGet(queryContext: QueryContext): Promise | void;
// $beforeDelete(queryContext: QueryContext): Promise | void;
// $afterDelete(queryContext: QueryContext): Promise | void;
}

type GraphModel =
| ({ "#id"?: string; "#ref"?: never; "#dbRef"?: never } & T)
| ({ "#id"?: never; "#ref": string; "#dbRef"?: never } & { [P in keyof T]?: never })
| ({ "#id"?: never; "#ref"?: never; "#dbRef": number } & { [P in keyof T]?: never });

type NonFunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

interface DeepPartialGraphArray extends Array> {}

type DeepPartialGraphModel =
| GraphModel<{ [P in NonFunctionPropertyNames]?: DeepPartialGraph | Partial;

type DeepPartialGraph = T extends any[] | ReadonlyArray
? DeepPartialGraphArray : T extends Model
? DeepPartialGraphModel
: T;

export interface InsertGraphOptions {
relate?: boolean | string[];
}

export interface QueryContext {
// transaction: Transaction;
}

export interface TableMetadata {
columns: Array;
}

export interface TableMetadataOptions {
table: string;
}

export interface FetchTableMetadataOptions {
// knex?: knex;
force?: boolean;
table?: string;
}

class R extends Model {
p?: P; // comment and it works
d!: D;
}

export class P extends Model {
// remove extends and things work
rig!: R; // comment this line and things work
}

export class D extends Model {
// remove extends Model and things work
rs!: R[]; // comment out this line and things work
}

async function main() {
const d = new D();
await R.query().insertGraph({
d: d // change this to just be d and problem goes away!
});
}

main().then(() => {
console.log("here");
});


Cool, started re-looking at this and it looks like both the two repros in this issue by @koskimas are now fixed on master - It looks like the PR which fixed it is probably https://github.com/microsoft/TypeScript/pull/37589

@orta I used the included test project and confirmed it no longer errors in Version 3.9.0-dev.20200414

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  路  3Comments

Zlatkovsky picture Zlatkovsky  路  3Comments

weswigham picture weswigham  路  3Comments

wmaurer picture wmaurer  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments