diff --git a/doc/release-notes/changelog.md b/doc/release-notes/changelog.md index cf992e3f8..1b8a980b8 100644 --- a/doc/release-notes/changelog.md +++ b/doc/release-notes/changelog.md @@ -32,6 +32,7 @@ - `insertGraph()`, `upsertGraph()` and their `*AndFetch()` variants throw an error for unknown graph options like `noInset` instead of ignoring them with a warning, and `insertGraph()` also for the options of `upsertGraph()` that have no effect on an insert, like `noDelete` or `allowMarkers`. See [migration](/release-notes/migration.md#unknown-graph-options-throw) [#84](https://github.com/ditojs/objection/issues/84) - The type parameter of `RelateQueryBuilder` is the number query builder, e.g. `RelateQueryBuilder>` in custom query builders that override `relate()`. See [migration](/release-notes/migration.md#typescript-relatequerybuilder-takes-the-number-query-builder) - Custom query builders declare one `RebindType` member instead of the five `ArrayQueryBuilderType`, `SingleQueryBuilderType`, `MaybeSingleQueryBuilderType`, `NumberQueryBuilderType` and `PageQueryBuilderType` members, which are removed: `declare RebindType: MyQueryBuilder`, with the extra types `X` as third type parameter. The helper types `SingleQueryBuilder` etc. take a query builder, and `WithGraphModelQueryBuilder` and `ReturningMethod` are removed. See [migration](/release-notes/migration.md#typescript-custom-query-builders-declare-rebindtype) [#125](https://github.com/ditojs/objection/issues/125) [#319](https://github.com/Vincit/objection.js/issues/319) +- The internal helper types of the relation expression parser, the `withGraph*()` narrowing and the property names, like `ParseRelationString`, `WithGraph` and `RelationPropertyNames`, are no longer exported, and the unused `ItemType` is removed. See [migration](/release-notes/migration.md#typescript-internal-helper-types-are-no-longer-exported) - `static jsonSchema` is typed `ReadonlyJSONSchema`, a JSON schema with readonly arrays, so that it takes schemas declared `as const`. A model's schema read through the static type can't be passed where a `JSONSchema` is expected, unless the model declares `static jsonSchema: JSONSchema`. See [migration](/release-notes/migration.md#typescript-model-jsonschema-is-a-readonly-schema) [#16](https://github.com/ditojs/objection/issues/16) - `insert()`, `patch()`, `update()`, their `*AndFetch*()` and `*ById()` variants, `merge()` and `relate()` reject relation properties, which they ignore at runtime: they take a `PartialModelRow`, a model instance or its properties without relations, instead of `PartialModelObject`. Values whose type includes the relation properties are rejected too, e.g. `Partial`, `ModelObject` or `PartialModelObject` of models with relations. `insertGraph()` and `upsertGraph()` take relations. See [migration](/release-notes/migration.md#typescript-insert-patch-and-update-reject-relation-data) [#53](https://github.com/ditojs/objection/issues/53) - `distinctOn()` is typed to take column names, `raw()` and `ref()`, like `groupBy()`, instead of any selection, as it doesn't select its columns. diff --git a/doc/release-notes/migration.md b/doc/release-notes/migration.md index 84a641ae3..b1fa8e1a2 100644 --- a/doc/release-notes/migration.md +++ b/doc/release-notes/migration.md @@ -35,6 +35,7 @@ This section lists the breaking changes in objection 4.0. - [typescript: insertGraph() has no allowMarkers option](#typescript-insertgraph-has-no-allowmarkers-option) - [typescript: RelateQueryBuilder takes the number query builder](#typescript-relatequerybuilder-takes-the-number-query-builder) - [typescript: Custom query builders declare RebindType](#typescript-custom-query-builders-declare-rebindtype) +- [typescript: Internal helper types are no longer exported](#typescript-internal-helper-types-are-no-longer-exported) - [typescript: Model.jsonSchema is a readonly schema](#typescript-model-jsonschema-is-a-readonly-schema) - [typescript: insert(), patch() and update() reject relation data](#typescript-insert-patch-and-update-reject-relation-data) - [typescript: Property types leave out $ properties](#typescript-property-types-leave-out-properties) @@ -370,6 +371,10 @@ The third type parameter `X` holds extra types of the query, which strict mode u The helper types `SingleQueryBuilder`, `MaybeSingleQueryBuilder`, `ArrayQueryBuilder`, `NumberQueryBuilder` and `PageQueryBuilder` take any query builder, e.g. `MaybeSingleQueryBuilder` as the return type of an overridden `findById()`, and `NumberQueryBuilder>` for `patchById()`, `updateById()` and `deleteById()`, the writes of `findById()`. `WithGraphModelQueryBuilder` is removed. Use `QueryBuilderType>` instead. The `ReturningMethod` interface is removed too, `returning()` is a method of `QueryBuilder`. On query builders with an `any` result, e.g. `AnyQueryBuilder`, it returns `QueryBuilder | QueryBuilder` instead of `QueryBuilder`. +## typescript: Internal helper types are no longer exported + +The helper types of the relation expression parser, the `withGraph*()` narrowing and the property names, like `ParseRelationString`, `WithGraph`, `WithGraphProperty`, `InsertablePropertyNames` and `RelationPropertyNames`, are no longer exported, and the unused `ItemType` is removed. Use `WithGraphModel`, `WithGraphQueryBuilder`, `ModelProps` and `ModelRelations` instead. + ## typescript: Model.jsonSchema is a readonly schema `static jsonSchema` is typed `ReadonlyJSONSchema`, a JSON schema with readonly arrays, so that schemas declared `as const` can be assigned to it, for `FromJSONSchema`. A `JSONSchema` is assignable to it, but not the other way around: a model's schema read through the static type can't be passed to a parameter typed `JSONSchema`, or returned from `$beforeValidate()`. Declare the type on models that are used that way: diff --git a/typings/objection/index.d.ts b/typings/objection/index.d.ts index b29e500bc..689c41209 100644 --- a/typings/objection/index.d.ts +++ b/typings/objection/index.d.ts @@ -214,6 +214,7 @@ export type RelationExpression = string | object; // Type-level parsing of relation expressions, used to narrow the result // types of `withGraphFetched()`, `withGraphJoined()` and `fetchGraph()`. +// The types that declarations of inferred query types refer to are exported. /** * Parses a relation expression, in string or object notation, into a tree @@ -234,9 +235,9 @@ export type ParseRelationExpression = [E] extends [never] ? ParseRelationString : ParseRelationObject; -export type Whitespace = ' ' | '\n' | '\r' | '\t'; +type Whitespace = ' ' | '\n' | '\r' | '\t'; -export type Trim = S extends `${Whitespace}${infer R}` +type Trim = S extends `${Whitespace}${infer R}` ? Trim : S extends `${infer L}${Whitespace}` ? Trim @@ -245,7 +246,7 @@ export type Trim = S extends `${Whitespace}${infer R}` /** * Counts the occurrences of character C in S, as a tuple length. */ -export type CountChar< +type CountChar< S extends string, C extends string, N extends 0[] = [], @@ -254,7 +255,7 @@ export type CountChar< /** * True if S has as many opening as closing brackets and parentheses. */ -export type IsBalanced = +type IsBalanced = CountChar extends CountChar ? CountChar extends CountChar ? true @@ -265,7 +266,7 @@ export type IsBalanced = * Splits a list of expressions at the commas that aren't nested inside * brackets or parentheses: 'a, b.[c, d]' -> ['a', 'b.[c, d]']. */ -export type SplitList< +type SplitList< S extends string, Acc extends string = '', Out extends string[] = [], @@ -275,7 +276,7 @@ export type SplitList< : SplitList : [...Out, `${Acc}${S}`]; -export type ParseRelationList = L extends [ +type ParseRelationList = L extends [ infer H extends string, ...infer T extends string[], ] @@ -285,7 +286,7 @@ export type ParseRelationList = L extends [ /** * Parses '[a, b]', 'a.b' and 'a' style expressions. */ -export type ParseRelationString = +type ParseRelationString = Trim extends `[${infer Inner}]` ? ParseRelationList> : Trim extends `${infer Head}.${infer Rest}` @@ -299,7 +300,7 @@ export type ParseRelationString = * 'pets(selectName)' -> 'pets'. Returns never for nodes that can't be * narrowed: aliases ('pets as p'), '*' and recursion ('^', '^2'). */ -export type RelationName = +type RelationName = Trim extends `${infer Name}(${string})` ? RelationName : Trim extends infer Name extends string @@ -310,7 +311,7 @@ export type RelationName = : Name : never; -export type RelationNode = [Name] extends [never] +type RelationNode = [Name] extends [never] ? {} : { [K in Name]: Children }; @@ -319,26 +320,27 @@ export type RelationNode = [Name] extends [never] * `$` are options, and aliased nodes (`{ p: { $relation: 'pets' } }`) are * skipped. */ -export type ParseRelationObject = string extends keyof E - ? {} - : { - [ - K in keyof E as K extends `$${string}` - ? never - : false extends E[K] +type ParseRelationObject = + HasOnlyLiteralKeys extends true + ? { + [ + K in keyof E as K extends `$${string}` ? never - : E[K] extends { $relation: any } + : false extends E[K] ? never - : K - ]: E[K] extends object ? ParseRelationObject : {}; - }; + : E[K] extends { $relation: any } + ? never + : K + ]: E[K] extends object ? ParseRelationObject : {}; + } + : {}; /** * Marks the relations in tree T as fetched on model M: they become required * and are narrowed recursively. Only `undefined` is removed: declared `null` * is kept, as to-one relations can be null when there is no related row. */ -export type WithGraph = [keyof T] extends [never] +type WithGraph = [keyof T] extends [never] ? M : WithGraphRelations>; @@ -362,7 +364,7 @@ export type FetchedRelations = { [K in keyof M & keyof T]: NonNullable extends Model | Model[] ? K : never; }[keyof M & keyof T]; -export type WithGraphProperty = [keyof T] extends [never] +type WithGraphProperty = [keyof T] extends [never] ? P : P extends Array ? WithGraph[] @@ -466,11 +468,6 @@ type ItemOf = R extends readonly (infer I)[] ? R : M; -/** - * If T is an array, returns the item type, otherwise returns T. - */ -export type ItemType = T extends Array ? T[number] : T; - /** * Type for keys of non-function properties of T. */ @@ -550,7 +547,7 @@ export type PartialModelObject = { * builders for models with narrowed relations (see WithGraphModel) remain * assignable to the query builders for the plain models. */ -export type PartialModelProps = { +type PartialModelProps = { [K in DataPropertyNames]?: K extends RelationPropertyNames ? never : Expression; }; @@ -622,7 +619,7 @@ export interface GeneratedBrand { /** * Insert keys are optional for optional, nullable and generated properties. */ -export type OptionalInsertPropertyNames = { +type OptionalInsertPropertyNames = { [K in keyof M]-?: {} extends Pick ? K : null extends M[K] @@ -632,7 +629,7 @@ export type OptionalInsertPropertyNames = { : never; }[keyof M]; -export type InsertablePropertyNames = Exclude, RelationPropertyNames>; +type InsertablePropertyNames = Exclude, RelationPropertyNames>; /** * The complete data of a new row of model M, to check what is passed to @@ -656,9 +653,9 @@ export type InsertableGraph = Insertable & [K in RelationPropertyNames]?: InsertableGraphField; }; -export type InsertableGraphReference = { '#dbRef': MaybeCompositeId } | { '#ref': string }; +type InsertableGraphReference = { '#dbRef': MaybeCompositeId } | { '#ref': string }; -export type InsertableGraphField = +type InsertableGraphField = NonNullable extends (infer I extends Model)[] ? (InsertableGraph | InsertableGraphReference)[] : NonNullable extends infer I extends Model @@ -1119,7 +1116,7 @@ type ExtractedProperties = { type SelectQueryBuilder = NarrowRows< QB, SelectedRow, C>, - true extends SelectsAll ? true : false + true extends IsSelectAll ? true : false >; /** @@ -1200,7 +1197,7 @@ type SelectBase = All extends true // unknown types, under any aliases. type UnknownSelection = { [key: string]: unknown } & SelectedBrand<{ [key: string]: true }>; -type SelectsAll = X extends '*' | `${string}.*` ? true : false; +type IsSelectAll = X extends '*' | `${string}.*` ? true : false; /** * Replaces the item of the result R with the row. @@ -1312,9 +1309,9 @@ type CheckRelationExpression = IfStric type CheckModifierExpression = HasQueryClass extends true - ? [CheckedModifiers] extends [never] + ? [UnknownExpressionModifiers] extends [never] ? unknown - : { '~unknownModifier': CheckedModifiers } + : { '~unknownModifier': UnknownExpressionModifiers } : unknown; // Queries in generic code, where the model class can't be resolved, aren't @@ -1327,7 +1324,7 @@ type HasQueryClass = : true; // Only expressions with modifiers are checked for them. -type CheckedModifiers = E extends `${string}(${string}` | object +type UnknownExpressionModifiers = E extends `${string}(${string}` | object ? UnknownModifiers< ClassMappings>, ParseModifierExpression, @@ -1404,17 +1401,17 @@ type PropertyId = // Modifiers with their general type, e.g. inherited from `Model`, aren't // checked. -type ChecksModifiers = IfStrict< +type ShouldCheckModifiers = IfStrict< false, - HasQueryClass extends true ? ChecksClassModifiers>> : false + HasQueryClass extends true ? HasOnlyLiteralKeys>> : false >; -type ChecksClassModifiers = string extends keyof O ? false : true; +type HasOnlyLiteralKeys = string extends keyof O ? false : true; // The names that `modify()` takes on query builder QB, also in lists. Names // in variables typed `string` are rejected where they are checked. type ModifierName = - ChecksModifiers extends true ? keyof KnownModifiers & string : string; + ShouldCheckModifiers extends true ? keyof KnownModifiers & string : string; type ModifyArg = | ModifierName @@ -1430,7 +1427,7 @@ type UnknownModifiers = T extends unknown ? MappedClass extends infer C ? [C] extends [never] ? never - : | (ChecksClassModifiers> extends true + : | (HasOnlyLiteralKeys> extends true ? Exclude | P> : never) | UnknownModifiers, Children, P> @@ -1440,15 +1437,16 @@ type UnknownModifiers = T extends unknown : never; // Mappings with their general type, e.g. inherited from `Model`, give no class. -type MappedClass = string extends keyof Mappings - ? never - : K extends keyof Mappings - ? Mappings[K] extends { modelClass: infer C } - ? C extends abstract new (...args: any) => any - ? C - : C extends () => infer RC - ? RC - : never +type MappedClass = + HasOnlyLiteralKeys extends true + ? K extends keyof Mappings + ? Mappings[K] extends { modelClass: infer C } + ? C extends abstract new (...args: any) => any + ? C + : C extends () => infer RC + ? RC + : never + : never : never : never; @@ -1490,28 +1488,29 @@ type ModifierNode = [RelationName] extends [never type ModifierNames = Trim extends `${string}(${infer Args})` ? Exclude[number]>, ''> : never; -type ParseModifierObject = string extends keyof E - ? {} - : { - [ - K in keyof E as K extends `$${string}` - ? never - : false extends E[K] +type ParseModifierObject = + HasOnlyLiteralKeys extends true + ? { + [ + K in keyof E as K extends `$${string}` ? never - : E[K] extends { $relation: any } + : false extends E[K] ? never - : K - ]: { - names: E[K] extends { $modify: infer N } - ? N extends readonly (infer I extends string)[] - ? I - : N extends string - ? N - : never - : never; - children: E[K] extends object ? ParseModifierObject : {}; - }; - }; + : E[K] extends { $relation: any } + ? never + : K + ]: { + names: E[K] extends { $modify: infer N } + ? N extends readonly (infer I extends string)[] + ? I + : N extends string + ? N + : never + : never; + children: E[K] extends object ? ParseModifierObject : {}; + }; + } + : {}; export interface ForClassMethod { (modelClass: ModelConstructor): QueryBuilderType; @@ -2807,11 +2806,11 @@ export interface BelongsToManyRelationType extends RelationType { * Names of the properties of M that hold related models. Properties typed * `any` or `any[]` are columns. */ -export type RelationPropertyNames = { - [K in keyof M]-?: HoldsModels> extends true ? K : never; +type RelationPropertyNames = { + [K in keyof M]-?: HasModels> extends true ? K : never; }[keyof M]; -type HoldsModels

= [P] extends [readonly (infer I)[]] ? IsModelLike : IsModelLike

; +type HasModels

= [P] extends [readonly (infer I)[]] ? IsModelLike : IsModelLike

; type IsModelLike

= 0 extends 1 & P ? false : [P] extends [ModelLike] ? true : false; @@ -2820,7 +2819,7 @@ type IsModelLike

= 0 extends 1 & P ? false : [P] extends [ModelLike] ? true : // Spreads of instances and their JSON don't have it. type ModelLike = { $clone(opt?: CloneOptions): unknown }; -export type TypedModelClassSpecifier = +type TypedModelClassSpecifier = string | ModelConstructor | (() => ModelConstructor); /** @@ -2834,7 +2833,7 @@ export interface TypedRelationMapping< modelClass: TypedModelClassSpecifier; } -export type TypedRelationMappingFor = +type TypedRelationMappingFor = NonNullable extends (infer I extends Model)[] ? TypedRelationMapping< I, @@ -3631,13 +3630,14 @@ type ModelSchemaProperties = ReadonlyJSONSchema extends S // Relations are picked by their mappings, as resolving the related models // while the model's own type is built would make it its own base type. -type ModelRelationProperties infer M ? M : T> = string extends keyof R - ? {} - : { - -readonly [K in keyof R as IsMappedRelation extends true ? K : never]?: MappedRelation< - R[K] - >; - }; +type ModelRelationProperties infer M ? M : T> = + HasOnlyLiteralKeys extends true + ? { + -readonly [ + K in keyof R as IsMappedRelation extends true ? K : never + ]?: MappedRelation; + } + : {}; // Relation types without a brand and module paths can't be resolved. type IsMappedRelation = M extends { relation: infer T; modelClass: infer C }