diff --git a/lib/server.js b/lib/server.js index d2a5d5bf7..b5c7e10f3 100755 --- a/lib/server.js +++ b/lib/server.js @@ -120,7 +120,7 @@ internals.Server = class { const existing = this._core.decorations[type].get(property); if (options.extend) { - Hoek.assert(type !== 'handler', 'Cannot extent handler decoration:', propertyName); + Hoek.assert(type !== 'handler', 'Cannot extend handler decoration:', propertyName); Hoek.assert(existing, `Cannot extend missing ${type} decoration: ${propertyName}`); Hoek.assert(typeof method === 'function', `Extended ${type} decoration method must be a function: ${propertyName}`); @@ -142,7 +142,7 @@ internals.Server = class { // Request - Hoek.assert(!this._core.Request.reserved.includes(property), 'Cannot override built-in request interface decoration:', propertyName); + Hoek.assert(!this._core.Request.reserved.includes(property), 'Cannot override the built-in request interface decoration:', propertyName); if (options.apply) { this._core.decorations.requestApply = this._core.decorations.requestApply ?? new Map(); @@ -156,14 +156,14 @@ internals.Server = class { // Response - Hoek.assert(!this._core.Response.reserved.includes(property), 'Cannot override built-in response interface decoration:', propertyName); + Hoek.assert(!this._core.Response.reserved.includes(property), 'Cannot override the built-in response interface decoration:', propertyName); this._core.Response.prototype[property] = method; } else if (type === 'toolkit') { // Toolkit - Hoek.assert(!Toolkit.reserved.includes(property), 'Cannot override built-in toolkit decoration:', propertyName); + Hoek.assert(!Toolkit.reserved.includes(property), 'Cannot override the built-in toolkit decoration:', propertyName); this._core.toolkit.decorate(property, method); } else { diff --git a/lib/types/plugin.d.ts b/lib/types/plugin.d.ts index 2be5b9cf3..9847f8c8e 100644 --- a/lib/types/plugin.d.ts +++ b/lib/types/plugin.d.ts @@ -247,16 +247,9 @@ export interface HandlerDecorationMethod { defaults?: RouteOptions | ((method: any) => RouteOptions) | undefined; } -/** - * The general case for decorators added via server.decorate. - */ -export type DecorationMethod = (this: T, ...args: any[]) => any; - /** * An empty interface to allow typings of custom plugin properties. */ export interface PluginProperties { } - -export type DecorateName = string | symbol; diff --git a/lib/types/server/server.d.ts b/lib/types/server/server.d.ts index 8b1069fb0..4d64710e1 100644 --- a/lib/types/server/server.d.ts +++ b/lib/types/server/server.d.ts @@ -12,8 +12,6 @@ import { ServerRegisterOptions, ServerRegisterPluginObject, ServerRegisterPluginObjectArray, - DecorateName, - DecorationMethod, HandlerDecorationMethod, PluginProperties } from '../plugin'; @@ -53,6 +51,57 @@ import { import { ServerOptions } from './options'; import { ServerState, ServerStateCookieOptions } from './state'; +/** + * The general case for decorators added via server.decorate. + */ +export type DecorationMethod = (this: T, ...args: any[]) => any; + +export type DecorateName = string | symbol; + +export type DecorationValue = object | any[] | boolean | number | string | symbol | Map | Set; + +type ReservedRequestKeys = ( + 'server' | 'url' | 'query' | 'path' | 'method' | + 'mime' | 'setUrl' | 'setMethod' | 'headers' | 'id' | + 'app' | 'plugins' | 'route' | 'auth' | 'pre' | + 'preResponses' | 'info' | 'isInjected' | 'orig' | + 'params' | 'paramsArray' | 'payload' | 'state' | + 'response' | 'raw' | 'domain' | 'log' | 'logs' | + 'generateResponse' | + + // Private functions + '_allowInternals' | '_closed' | '_core' | + '_entity' | '_eventContext' | '_events' | '_expectContinue' | + '_isInjected' | '_isPayloadPending' | '_isReplied' | + '_route' | '_serverTimeoutId' | '_states' | '_url' | + '_urlError' | '_initializeUrl' | '_setUrl' | '_parseUrl' | + '_parseQuery' + +); + +type ReservedToolkitKeys = ( + 'abandon' | 'authenticated' | 'close' | 'context' | 'continue' | + 'entity' | 'redirect' | 'realm' | 'request' | 'response' | + 'state' | 'unauthenticated' | 'unstate' +); + +type ReservedServerKeys = ( + // Public functions + 'app' | 'auth' | 'cache' | 'decorations' | 'events' | 'info' | + 'listener' | 'load' | 'methods' | 'mime' | 'plugins' | 'registrations' | + 'settings' | 'states' | 'type' | 'version' | 'realm' | 'control' | 'decoder' | + 'bind' | 'control' | 'decoder' | 'decorate' | 'dependency' | 'encoder' | + 'event' | 'expose' | 'ext' | 'inject' | 'log' | 'lookup' | 'match' | 'method' | + 'path' | 'register' | 'route' | 'rules' | 'state' | 'table' | 'validator' | + 'start' | 'initialize' | 'stop' | + + // Private functions + '_core' | '_initialize' | '_start' | '_stop' | '_cachePolicy' | '_createCache' | + '_clone' | '_ext' | '_addRoute' +); + +type ExceptName = Property extends ReservedKeys ? never : Property; + /** * User-extensible type for application specific state (`server.app`). */ @@ -309,21 +358,24 @@ export class Server { * @return void; * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-serverdecoratetype-property-method-options) */ - decorate(type: 'handler', property: DecorateName, method: HandlerDecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'request', property: DecorateName, method: (existing: ((...args: any[]) => any)) => (request: Request) => DecorationMethod, options: {apply: true, extend: true}): void; - decorate(type: 'request', property: DecorateName, method: (request: Request) => DecorationMethod, options: {apply: true, extend?: boolean | undefined}): void; - decorate(type: 'request', property: DecorateName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'request', property: DecorateName, value: (existing: ((...args: any[]) => any)) => (request: Request) => any, options: {apply: true, extend: true}): void; - decorate(type: 'request', property: DecorateName, value: (request: Request) => any, options: {apply: true, extend?: boolean | undefined}): void; - decorate(type: 'request', property: DecorateName, value: any, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'toolkit', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationMethod, options: {apply?: boolean | undefined, extend: true}): void; - decorate(type: 'toolkit', property: DecorateName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'toolkit', property: DecorateName, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void; - decorate(type: 'toolkit', property: DecorateName, value: any, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'server', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationMethod, options: {apply?: boolean | undefined, extend: true}): void; - decorate(type: 'server', property: DecorateName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'server', property: DecorateName, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void; - decorate(type: 'server', property: DecorateName, value: any, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate

(type: 'handler', property: P, method: HandlerDecorationMethod, options?: { apply?: boolean | undefined, extend?: never }): void; + + decorate

(type: 'request', property: ExceptName, method: (existing: ((...args: any[]) => any)) => (request: Request) => DecorationMethod, options: {apply: true, extend: true}): void; + decorate

(type: 'request', property: ExceptName, method: (request: Request) => DecorationMethod, options: {apply: true, extend?: boolean | undefined}): void; + decorate

(type: 'request', property: ExceptName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate

(type: 'request', property: ExceptName, value: (existing: ((...args: any[]) => any)) => (request: Request) => any, options: {apply: true, extend: true}): void; + decorate

(type: 'request', property: ExceptName, value: (request: Request) => any, options: {apply: true, extend?: boolean | undefined}): void; + decorate

(type: 'request', property: ExceptName, value: DecorationValue, options?: never): void; + + decorate

(type: 'toolkit', property: ExceptName, method: (existing: ((...args: any[]) => any)) => DecorationMethod, options: {apply?: boolean | undefined, extend: true}): void; + decorate

(type: 'toolkit', property: ExceptName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate

(type: 'toolkit', property: ExceptName, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void; + decorate

(type: 'toolkit', property: ExceptName, value: DecorationValue, options?: never): void; + + decorate

(type: 'server', property: ExceptName, method: (existing: ((...args: any[]) => any)) => DecorationMethod, options: {apply?: boolean | undefined, extend: true}): void; + decorate

(type: 'server', property: ExceptName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate

(type: 'server', property: ExceptName, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void; + decorate

(type: 'server', property: ExceptName, value: DecorationValue, options?: never): void; /** * Used within a plugin to declare a required dependency on other plugins where: diff --git a/test/server.js b/test/server.js index 929450f09..94e95fc0a 100755 --- a/test/server.js +++ b/test/server.js @@ -592,7 +592,7 @@ describe('Server', () => { expect(() => { server.decorate('toolkit', 'redirect', () => { }); - }).to.throw('Cannot override built-in toolkit decoration: redirect'); + }).to.throw('Cannot override the built-in toolkit decoration: redirect'); }); it('decorates server', async () => { diff --git a/test/types/index.ts b/test/types/index.ts index 988edffb0..4a438b38d 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -10,7 +10,8 @@ import { Server, ServerRoute, server as createServer, - ServerRegisterPluginObject + ServerRegisterPluginObject, + Lifecycle } from '../..'; const { expect: check } = lab; @@ -123,4 +124,94 @@ server.method('test.add', (a: number, b: number) => a + b, { segment: 'test-segment', }, generateKey: (a: number, b: number) => `${a}${b}` -}); \ No newline at end of file +}); + +declare module '../..' { + interface Request { + obj1: { + func1(a: number, b: number): number; + }; + + func2: (a: number, b: number) => number; + } + + interface ResponseToolkit { + obj2: { + func3(a: number, b: number): number; + }; + + func4: (a: number, b: number) => number; + } + + interface Server { + obj3: { + func5(a: number, b: number): number; + }; + + func6: (a: number, b: number) => number; + } +} + +const theFunc = (a: number, b: number) => a + b; +const theLifecycleMethod: Lifecycle.Method = () => 'ok'; + +// Error when decorating existing properties +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('request', 'payload', theFunc)); +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('toolkit', 'state', theFunc)); +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('server', 'dependency', theFunc)); +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('server', 'dependency', theFunc)); + +server.decorate('handler', 'func1_1', () => theLifecycleMethod); +server.decorate('handler', 'func1_2', () => theLifecycleMethod, { apply: true }); + +// Error when extending on handler +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('handler', 'func1_3', () => theLifecycleMethod, { apply: true, extend: true })); + +// Error when handler does not return a lifecycle method +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('handler', 'func1_4', theFunc)); + +// Decorating request with functions +server.decorate('request', 'func2_1', theFunc); +server.decorate('request', 'func2_1', () => theFunc, { apply: true, extend: true }); +server.decorate('request', 'func2_2', theFunc, { apply: true }); +server.decorate('request', 'func2_2', theFunc, { extend: true }); + +// Decorating toolkit with functions +server.decorate('toolkit', 'func4_1', theFunc); +server.decorate('toolkit', 'func4_1', theFunc, { apply: true, extend: true }); +server.decorate('toolkit', 'func4_2', theFunc, { apply: true }); +server.decorate('toolkit', 'func4_2', theFunc, { extend: true }); + +// Decorating server with functions +server.decorate('server', 'func6_1', theFunc); +server.decorate('server', 'func6_1', theFunc, { apply: true, extend: true }); +server.decorate('server', 'func6_2', theFunc, { apply: true }); +server.decorate('server', 'func6_2', theFunc, { extend: true }); + +// Decorating request with objects +server.decorate('request', 'obj1_1', { func1: theFunc }); + +// Type error when extending on request with objects +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('request', 'obj1_1', { func1: theFunc }, { apply: true, extend: true })); + + +// Decorating toolkit with objects +server.decorate('toolkit', 'obj2_1', { func3: theFunc }); + +// Error when extending on toolkit with objects +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('toolkit', 'obj2_1', { func3: theFunc }, { apply: true, extend: true })); + +// Decorating server with objects +server.decorate('server', 'obj3_1', { func5: theFunc }); + +// Error when extending on server with objects +// @ts-expect-error Lab does not support overload errors +check.error(() => server.decorate('server', 'obj3_1', { func5: theFunc }, { apply: true, extend: true }));