diff --git a/README.md b/README.md index eb73dd7..3d3b5dd 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,24 @@ A developers goodie for [Strapi Headless CMS](https://github.com/strapi/strapi) yarn add -D strapi-typed@latest ``` -and then use +After install typings should be automatically available if you import from `@strapi/strapi`. + +If you want to use are internal utility types then use + +```typescript +import { StringMap, OnlyStrings } from "@strapi/typed"; +``` + +In few place plugin creator or Strapi consumer can extend couple of interfaces to add custom typings + +For example ```typescript -import { IStrapi } from 'strapi-typed'; +declare module '@strapi/strapi' { + export interface IStrapiRequestQueryFiltersExtra { + threadOf?: number | string | null; + } +} ``` Enjoy 🎉 diff --git a/types/api.ts b/types/api.ts index 1559f89..99cb021 100644 --- a/types/api.ts +++ b/types/api.ts @@ -1,93 +1,150 @@ -import { StringMap, Primitive, TypeResult } from "./common"; -import { PopulateClause, StrapiUser } from "./core"; -import { OnlyStrings } from "./utils"; - -type SendStrapiContextFunction = (...args: unknown[]) => void; - -type ThrowStrapiContextFunction = (...args: unknown[]) => void; - -type StrapiHTTPErrorConstructor> = (message?: string, details?: T) => any - -export type StrapiRequestContext< - TBody extends {} = StringMap, - TQuery extends {} = StringMap, - TParams extends {} = StringMap -> = { - request: StrapiRequest; - query: TQuery; - params: TParams; - pagination: StrapiPagination; - sort: StringMap; - state: StrapiRequestContextState; - - send: SendStrapiContextFunction; - throw: ThrowStrapiContextFunction; - - // @see {https://github.com/jshttp/http-errors#list-of-all-constructors} - notFound: StrapiHTTPErrorConstructor; - badRequest: StrapiHTTPErrorConstructor; - unauthorized: StrapiHTTPErrorConstructor; - methodNotAllowed: StrapiHTTPErrorConstructor; - internalServerError: StrapiHTTPErrorConstructor; - notImplemented: StrapiHTTPErrorConstructor; -}; - -export type StrapiRequest = { - body?: TBody; -}; - -export type StrapiRequestContextState = { - user?: StrapiUser -}; - -export type StrapiRequestQuery = { - filters?: { - threadOf?: number | string | null; - [key: string]: any; - } & {}; - populate?: StrapiRequestQueryPopulateClause>; - sort?: StringMap; - fields?: StrapiRequestQueryFieldsClause>; - pagination?: StrapiPagination; -} - -export type StrapiRequestQueryFieldsClause = Array | '*'; - -export type StrapiRequestQueryPopulateClause = PopulateClause; - -export type StrapiPagination = { - page?: number; - pageSize?: number; - start?: number; - limit?: number; - withCount?: boolean | string; -}; - -export type StrapiResponseMetaPagination = TypeResult< - Pick & { - pageCount?: number; - total?: number; +import { StringMap, OnlyStrings, TypeResult, Primitive } from "@strapi/typed"; + +declare module "@strapi/strapi" { + type SendStrapiContextFunction = (...args: unknown[]) => void; + + type ThrowStrapiContextFunction = (...args: unknown[]) => void; + + type StrapiHTTPErrorConstructor> = ( + message?: string, + details?: T + ) => any; + + export type StrapiRequestContext< + TBody extends {} = StringMap, + TQuery extends {} = StringMap, + TParams extends {} = StringMap + > = { + request: StrapiRequest; + query: TQuery; + params: TParams; + pagination: StrapiPagination; + sort: StringMap; + state: StrapiRequestContextState; + + send: SendStrapiContextFunction; + throw: ThrowStrapiContextFunction; + + // @see {https://github.com/jshttp/http-errors#list-of-all-constructors} + notFound: StrapiHTTPErrorConstructor; + badRequest: StrapiHTTPErrorConstructor; + unauthorized: StrapiHTTPErrorConstructor; + methodNotAllowed: StrapiHTTPErrorConstructor; + internalServerError: StrapiHTTPErrorConstructor; + notImplemented: StrapiHTTPErrorConstructor; + }; + + export type StrapiRequest = { + body?: TBody; + }; + + export type StrapiRequestContextState = { + user?: StrapiUser; + }; + + export type StrapiRequestQuery = { + filters?: { + threadOf?: number | string | null; + [key: string]: any; + } & {}; + populate?: StrapiRequestQueryPopulateClause>; + sort?: StringMap; + fields?: StrapiRequestQueryFieldsClause>; + pagination?: StrapiPagination; + }; + + export type StrapiRequestQueryFieldsClause = + | Array + | "*"; + + export type StrapiRequestQueryPopulateClause = + PopulateClause; + interface ISendStrapiContextFunction { + (...args: unknown[]): void; } ->; - -export type StrapiResponseMeta = { - pagination: StrapiResponseMetaPagination; -}; -export type StrapiPaginatedResponse = { - data: Array; - meta?: StrapiResponseMeta; -}; + interface IThrowStrapiContextFunction { + (...args: unknown[]): void; + } -export type StrapiQueryParams = StringMap; + export interface IStrapiRequestContext< + TBody extends {} = StringMap, + TQuery extends {} = StringMap, + TParams extends {} = StringMap + > { + request: StrapiRequest; + query: TQuery; + params: TParams; + pagination: StrapiPagination; + sort: StringMap; + state: StrapiRequestContextState; + + send: ISendStrapiContextFunction; + throw: IThrowStrapiContextFunction; + } -export type StrapiQueryParamsParsed = StringMap; + export interface IStrapiController< + TBody extends {} = StringMap, + TQuery extends {} = StringMap, + TParams extends {} = StringMap, + TResult = unknown + > { + (ctx: IStrapiRequestContext): TResult; + } -export type StrapiQueryParamsParsedOrderBy = string | Array; + export interface IStrapiRequestQueryFiltersExtra {} -type StrapiQueryParamsParsedFilterValue = - | Primitive - | Partial>; + export interface IStrapiRequestQuery { + filters?: { + [key: string]: any; + } & IStrapiRequestQueryFiltersExtra; + populate?: StrapiRequestQueryPopulateClause>; + sort?: StringMap; + fields?: StrapiRequestQueryFieldsClause>; + pagination?: StrapiPagination; + } -export type StrapiQueryParamsParsedFilters = -Partial, StrapiQueryParamsParsedFilterValue>>>; + export type StrapiPagination = { + page?: number; + pageSize?: number; + start?: number; + limit?: number; + withCount?: boolean | string; + }; + + export type StrapiResponseMetaPagination = TypeResult< + Pick & { + pageCount?: number; + total?: number; + } + >; + + export type StrapiResponseMeta = { + pagination: StrapiResponseMetaPagination; + }; + + export type StrapiPaginatedResponse = { + data: Array; + meta?: StrapiResponseMeta; + }; + + export type StrapiQueryParams = StringMap; + + export type StrapiQueryParamsParsed = StringMap; + + export type StrapiQueryParamsParsedOrderBy = string | Array; + + type StrapiQueryParamsParsedFilterValue = + | Primitive + | Partial>; + + export type StrapiQueryParamsParsedFilters< + TValue, + TKeys = keyof TValue + > = Partial< + Record< + OnlyStrings, + StrapiQueryParamsParsedFilterValue> + > + >; +} diff --git a/types/common.d.ts b/types/common.d.ts index 4e24b61..ec09894 100644 --- a/types/common.d.ts +++ b/types/common.d.ts @@ -1,15 +1,17 @@ -export type PropType = TObj[TProp]; +declare module "@strapi/typed" { + export type PropType = TObj[TProp]; -export type Primitive = string | number | object | boolean | null | undefined; + export type Primitive = string | number | object | boolean | null | undefined; -export type KeyValueSet = Record; + export type KeyValueSet = Record; -export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + export type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; -export type StringMap = Record; + export type StringMap = Record; -export type TypeResult = { - [K in keyof T]: T[K]; -}; + export type TypeResult = { + [K in keyof T]: T[K]; + }; -export type Id = number | string; \ No newline at end of file + export type Id = number | string; +} diff --git a/types/contentType.ts b/types/contentType.ts index 88c2702..3d3b496 100644 --- a/types/contentType.ts +++ b/types/contentType.ts @@ -1,110 +1,115 @@ -import { Primitive, StringMap } from "./common"; +import { Primitive, StringMap } from "@strapi/typed"; -export type ContentType = "collectionType" | "singleType"; +declare module "@strapi/strapi" { + export type ContentType = "collectionType" | "singleType"; -export type StrapiContentTypeInfo = { - singularName: string; - pluralName: string; - displayName: string; - description: string; -}; + export interface IStrapiContentTypeInfo { + singularName: string; + pluralName: string; + displayName: string; + description: string; + } -export type StrapiContentTypeAttributeValidator = { - required?: boolean; - max?: number; - min?: number; - minLength?: number; - maxLength?: number; - private?: boolean; - configurable?: boolean; - default?: Primitive; -}; -export type SimpleStrapiContentTypeAttributeKeys = - | "string" - | "text" - | "richtext" - | "email" - | "password" - | "date" - | "time" - | "datetime" - | "timestamp" - | "boolean" - | "integer" - | "biginteger" - | "float" - | "decimal" - | "json"; + export interface IStrapiContentTypeAttributeValidator { + required?: boolean; + max?: number; + min?: number; + minLength?: number; + maxLength?: number; + private?: boolean; + configurable?: boolean; + default?: Primitive; + } -export type SimpleStrapiContentTypeAttribute = { - type: SimpleStrapiContentTypeAttributeKeys; -} & StrapiContentTypeAttributeValidator; + export type SimpleStrapiContentTypeAttributeKeys = + | "string" + | "text" + | "richtext" + | "email" + | "password" + | "date" + | "time" + | "datetime" + | "timestamp" + | "boolean" + | "integer" + | "biginteger" + | "float" + | "decimal" + | "json"; -export type StrapiContentTypeEnumerationAttribute = { - type: "enumeration"; - enum: Array; -} & StrapiContentTypeAttributeValidator; + export type SimpleStrapiContentTypeAttribute = { + type: SimpleStrapiContentTypeAttributeKeys; + } & IStrapiContentTypeAttributeValidator; -export type StrapiContentTypeComponentAttribute = { - type: "component"; - component: string; - repeatable?: boolean; -}; + export type StrapiContentTypeEnumerationAttribute = { + type: "enumeration"; + enum: Array; + } & IStrapiContentTypeAttributeValidator; -export type StrapiContentTypeDynamicZoneAttribute = { - type: "dynamiczone"; - components: Array; -}; + export type StrapiContentTypeComponentAttribute = { + type: "component"; + component: string; + repeatable?: boolean; + }; -export type StrapiContentTypeMediaAttribute = { - type: "media"; - allowedTypes: Array<"images" | "videos" | "files">; - required?: boolean; -}; + export type StrapiContentTypeDynamicZoneAttribute = { + type: "dynamiczone"; + components: Array; + }; -export type StrapiContentTypeUIDAttribute = { - type: "uid"; - targetField: T; - options: string; -}; + export type StrapiContentTypeMediaAttribute = { + type: "media"; + allowedTypes: Array<"images" | "videos" | "files">; + required?: boolean; + }; -export type StrapiContentTypeRelationType = - | "oneToOne" - | "oneToMany" - | "manyToOne" - | "manyToMany"; + export type StrapiContentTypeUIDAttribute = { + type: "uid"; + targetField: T; + options: string; + }; -export type StrapiContentTypeRelationAttribute = { - type: "relation"; - relation: StrapiContentTypeRelationType; - target: string; - mappedBy?: string; - inversedBy?: string; -}; + export type StrapiContentTypeRelationType = + | "oneToOne" + | "oneToMany" + | "manyToOne" + | "manyToMany"; -export type StrapiContentTypeAttributes = Record< - T, - | SimpleStrapiContentTypeAttribute - | StrapiContentTypeEnumerationAttribute - | StrapiContentTypeComponentAttribute - | StrapiContentTypeDynamicZoneAttribute - | StrapiContentTypeRelationAttribute - | StrapiContentTypeMediaAttribute ->; + export type StrapiContentTypeRelationAttribute = { + type: "relation"; + relation: StrapiContentTypeRelationType; + target: string; + mappedBy?: string; + inversedBy?: string; + }; -export type StrapiContentTypeFullSchema = { - kind: ContentType; - collectionName: string; - info: StrapiContentTypeInfo; - options: StringMap; - attributes: StrapiContentTypeAttributes; - actions: StringMap; - lifecycles: StringMap; - uid: string; - apiName: string; -}; + export type StrapiContentTypeAttributes = Record< + T, + | SimpleStrapiContentTypeAttribute + | StrapiContentTypeEnumerationAttribute + | StrapiContentTypeComponentAttribute + | StrapiContentTypeDynamicZoneAttribute + | StrapiContentTypeRelationAttribute + | StrapiContentTypeMediaAttribute + >; -export type StrapiContentTypeSchema = Pick< - StrapiContentTypeFullSchema, - "info" | "kind" | "attributes" | "options" ->; + export type StrapiContentTypeFullSchema = + { + kind: ContentType; + collectionName: string; + info: IStrapiContentTypeInfo; + options: StringMap; + attributes: StrapiContentTypeAttributes; + actions: StringMap; + lifecycles: StringMap; + uid: string; + apiName: string; + }; + + export type StrapiContentTypeSchema = + Pick< + StrapiContentTypeFullSchema, + "info" | "kind" | "attributes" | "options" + >; +} diff --git a/types/core.d.ts b/types/core.d.ts index 95d3dcb..5c7a707 100644 --- a/types/core.d.ts +++ b/types/core.d.ts @@ -1,10 +1,14 @@ -import { EventEmitter } from "events"; -import { StrapiRequestContext } from "./api"; -import { HTTPMethod, Primitive, StringMap, TypeResult } from "./common"; -import { StrapiContentTypeFullSchema, StrapiContentTypeSchema } from "./contentType"; -import { OnlyStrings } from "./utils"; +declare module "@strapi/strapi" { + import { EventEmitter } from "events"; + import { StrapiRequestContext } from "./api"; + import { HTTPMethod, Primitive, StringMap, TypeResult } from "./common"; + import { + StrapiContentTypeFullSchema, + StrapiContentTypeSchema, + } from "./contentType"; + import { OnlyStrings } from "./utils"; -export interface IStrapi { + export interface IStrapi { config: StrapiConfigContainer; EE(): Function; get services(): StringMap; @@ -57,53 +61,57 @@ export interface IStrapi { db: StrapiDB; admin: StrapiAdmin; log: StrapiLog; -} + } -export type StrapiEvents = `${'entry' | 'media'}.${StrapiEventsCrudFlow}` | `entry.${StrapiEventsPublishFlow}`; -type StrapiEventsCrudFlow = 'create' | 'update' | 'delete'; -type StrapiEventsPublishFlow = 'publish' | 'unpublish'; + export type StrapiEvents = + | `${"entry" | "media"}.${StrapiEventsCrudFlow}` + | `entry.${StrapiEventsPublishFlow}`; + type StrapiEventsCrudFlow = "create" | "update" | "delete"; + type StrapiEventsPublishFlow = "publish" | "unpublish"; -export type StrapiService = any; -export type StrapiController< - TResult = unknown, + export type StrapiService = any; + export type StrapiController< + TResult = unknown, TBody extends {} = StringMap, TQuery extends {} = StringMap, TParams extends {} = StringMap, TContextExtra extends {} = {} -> = (context: StrapiRequestContext & TContextExtra) => TResult -export type StrapiMiddleware = Object; -export type StrapiContentType = T; -export type StrapiPolicy = Object; -export type StrapiHook = Object; + > = ( + context: StrapiRequestContext & TContextExtra + ) => TResult; + export type StrapiMiddleware = Object; + export type StrapiContentType = T; + export type StrapiPolicy = Object; + export type StrapiHook = Object; -export type StrapiWebhook = { - id?: Id, - name: string, - type: string, - url: string, - headers: StringMap, - events: StrapiEvents[], - enabled: boolean, -} -export type StrapiWebhookRunner = { - config: StringMap, - eventHub: EventEmitter, - listeners: Map, - logger: unknown, - queue: unknown, - webhooksMap: Map, -}; -export type StrapiWebhookStore = { - createWebhook: (data: StrapiWebhook) => any - deleteWebhook: (id) => any - findWebhook: (id) => StrapiWebhook - findWebhooks: () => StrapiWebhook[] - updateWebhook: (data: StrapiWebhook) => any -} + export type StrapiWebhook = { + id?: Id; + name: string; + type: string; + url: string; + headers: StringMap; + events: StrapiEvents[]; + enabled: boolean; + }; + export type StrapiWebhookRunner = { + config: StringMap; + eventHub: EventEmitter; + listeners: Map; + logger: unknown; + queue: unknown; + webhooksMap: Map; + }; + export type StrapiWebhookStore = { + createWebhook: (data: StrapiWebhook) => any; + deleteWebhook: (id) => any; + findWebhook: (id) => StrapiWebhook; + findWebhooks: () => StrapiWebhook[]; + updateWebhook: (data: StrapiWebhook) => any; + }; -export type StrapiApi = Object; -export type StrapiAuth = Object; -export type StrapiPlugin = { + export type StrapiApi = Object; + export type StrapiAuth = Object; + export type StrapiPlugin = { get services(): StringMap; service(uid: string): StrapiService; get controllers(): StringMap; @@ -111,73 +119,79 @@ export type StrapiPlugin = { get contentTypes(): StringMap; contentType(name: string): StrapiContentType; config(name: string): any; -}; + }; -export type StrapiPluginConfig = TypeResult; + export type StrapiPluginConfig = TypeResult; -export type StrapiConfigContainer = StringMap & { + export type StrapiConfigContainer = StringMap & { get: Function; -}; + }; -export type StrapiStore = { + export type StrapiStore = { get: ({ key: T }) => U; set: ({ key: T, value: U }) => void; delete: ({ key: T }) => void; -}; + }; -export type StrapiStoreQuery = { + export type StrapiStoreQuery = { type: string; name?: string; -}; + }; -export type StrapiDB = { + export type StrapiDB = { query(uid: string): StrapiDBQuery; -}; + }; -export type StrapiDBQuery = { + export type StrapiDBQuery = { findOne(args: number | string | StrapiDBQueryArgs): Promise; findMany(args?: StrapiDBQueryArgs): Promise>; findWithCount( - args: StrapiDBQueryArgs + args: StrapiDBQueryArgs ): Promise<[items: Array, count: number]>; create(args: StrapiDBQueryArgs): Promise; - createMany(args: StrapiDBQueryArgs): Promise; + createMany( + args: StrapiDBQueryArgs + ): Promise; update(args: StrapiDBQueryArgs): Promise; - updateMany(args: StrapiDBQueryArgs): Promise; + updateMany( + args: StrapiDBQueryArgs + ): Promise; delete(args: StrapiDBQueryArgs): Promise; - deleteMany(args: StrapiDBQueryArgs): Promise; + deleteMany( + args: StrapiDBQueryArgs + ): Promise; count(args?: StrapiDBQueryArgs): number; -}; + }; -// https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#logical-operators + // https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#logical-operators -type EqualWhereOperator = { $eq: T }; -type NotEqualWhereOperator = { $ne: T }; -type InWhereOperator = { $in: Array }; -type NotInWhereOperator = { $notIn: Array }; -type LessThanWhereOperator = { $lt: number }; -type LessThanEqualWhereOperator = { $lte: number }; -type GreaterThanWhereOperator = { $gt: number }; -type GreaterThanEqualWhereOperator = { $gte: number }; -type BetweenWhereOperator = { $between: Array }; -type ContainsWhereOperator = { $contains: string }; -type NotContainsWhereOperator = { $notContains: string }; -type ContainsCaseInsensitiveWhereOperator = { $containsi: string }; -type NotContainsCaseInsensitiveWhereOperator = { $notContainsi: string }; -type StartsWithWhereOperator = { $startsWith: string }; -type EndsWithWhereOperator = { $endsWith: string }; -type EndsWithWhereOperator = { $endsWith: string }; -type IsNullWhereOperator = { $null: boolean }; -type IsNotNullWhereOperator = { $notNull: boolean }; -type NotWhereOperator = { + type EqualWhereOperator = { $eq: T }; + type NotEqualWhereOperator = { $ne: T }; + type InWhereOperator = { $in: Array }; + type NotInWhereOperator = { $notIn: Array }; + type LessThanWhereOperator = { $lt: number }; + type LessThanEqualWhereOperator = { $lte: number }; + type GreaterThanWhereOperator = { $gt: number }; + type GreaterThanEqualWhereOperator = { $gte: number }; + type BetweenWhereOperator = { $between: Array }; + type ContainsWhereOperator = { $contains: string }; + type NotContainsWhereOperator = { $notContains: string }; + type ContainsCaseInsensitiveWhereOperator = { $containsi: string }; + type NotContainsCaseInsensitiveWhereOperator = { $notContainsi: string }; + type StartsWithWhereOperator = { $startsWith: string }; + type EndsWithWhereOperator = { $endsWith: string }; + type EndsWithWhereOperator = { $endsWith: string }; + type IsNullWhereOperator = { $null: boolean }; + type IsNotNullWhereOperator = { $notNull: boolean }; + type NotWhereOperator = { $not: { - [TKey]: [TValue]; + [TKey]: [TValue]; }; -}; -type AndWhereOperator = { $and: T[] }; -type OrWhereOperator = { $or: T[] }; + }; + type AndWhereOperator = { $and: T[] }; + type OrWhereOperator = { $or: T[] }; -type WhereOperator = + type WhereOperator = | T | EqualWhereOperator | NotEqualWhereOperator @@ -198,21 +212,26 @@ type WhereOperator = | IsNullWhereOperator | IsNotNullWhereOperator; -type WhereClause = Partial< - Record> & - OrWhereOperator>>> & - AndWhereOperator>>> ->; + type WhereClause< + TKeys extends string = string, + TValues = Primitive + > = Partial< + Record> & + OrWhereOperator>>> & + AndWhereOperator>>> + >; -type PopulateClause = - Partial | StringMap>> | - Array | - boolean; + type PopulateClause = + | Partial | StringMap>> + | Array + | boolean; + type SelectClause = TKeys | Array | "*"; -type SelectClause = TKeys | Array | '*'; - -export type StrapiDBQueryArgs = { + export type StrapiDBQueryArgs< + TFields extends string = string, + TData = unknown + > = { select?: SelectClause>; where?: WhereClause>; data?: TData; @@ -220,47 +239,48 @@ export type StrapiDBQueryArgs limit?: number; populate?: PopulateClause>; orderBy?: string | Array; -}; + }; -export type StrapiDBBulkActionResponse = { + export type StrapiDBBulkActionResponse = { count: number; -}; + }; -export type StrapiAdmin = any; + export type StrapiAdmin = any; -export type StrapiLog = { + export type StrapiLog = { log: Function; error: Function; warn: Function; -}; + }; -export type StrapiRoute = { + export type StrapiRoute = { method: HTTPMethod; path: string; handler: string; config: StrapiRouteConfig; -}; + }; -export type StrapiRouteConfig = { + export type StrapiRouteConfig = { policies: Array; description?: string; - tag?: StrapiRouteConfigTag; -}; + tag?: StrapiRouteConfigTag; + }; -export type StrapiRouteConfigTag = { + export type StrapiRouteConfigTag = { plugin: string; name?: string; actionType?: string; -}; + }; -export type StrapiAdminUser = any; + export type StrapiAdminUser = any; -export type StrapiUser = { + export type StrapiUser = { id: Id; email: string; username: string; -} & StringMap; + } & StringMap; -export type StrapiContext = { + export type StrapiContext = { strapi: IStrapi; -}; + }; +} diff --git a/types/index.d.ts b/types/index.d.ts index 2794edf..99fb117 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,5 +1,5 @@ -export * from './common'; -export * from './contentType'; -export * from './core'; -export * from './api'; -export * from './utils'; \ No newline at end of file +import "./common"; +import "./contentType"; +import "./core"; +import "./api"; +import "./utils"; diff --git a/types/utils.ts b/types/utils.ts index 1879593..969e18c 100644 --- a/types/utils.ts +++ b/types/utils.ts @@ -1 +1,3 @@ -export type OnlyStrings = T extends string ? T : never; +declare module "@strapi/typed" { + export type OnlyStrings = T extends string ? T : never; +}