Skip to content

Commit c829767

Browse files
committed
fix: 🐛 typings around server.decorate
Types match the validation a bit more. Type errors with error when attempting to decorate a builtin.
1 parent 26390f2 commit c829767

File tree

4 files changed

+158
-22
lines changed

4 files changed

+158
-22
lines changed

lib/server.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ internals.Server = class {
120120

121121
const existing = this._core.decorations[type].get(property);
122122
if (options.extend) {
123-
Hoek.assert(type !== 'handler', 'Cannot extent handler decoration:', propertyName);
123+
Hoek.assert(type !== 'handler', 'Cannot extend handler decoration:', propertyName);
124124
Hoek.assert(existing, `Cannot extend missing ${type} decoration: ${propertyName}`);
125125
Hoek.assert(typeof method === 'function', `Extended ${type} decoration method must be a function: ${propertyName}`);
126126

@@ -142,7 +142,7 @@ internals.Server = class {
142142

143143
// Request
144144

145-
Hoek.assert(!this._core.Request.reserved.includes(property), 'Cannot override built-in request interface decoration:', propertyName);
145+
Hoek.assert(!this._core.Request.reserved.includes(property), 'Cannot override the built-in request interface decoration:', propertyName);
146146

147147
if (options.apply) {
148148
this._core.decorations.requestApply = this._core.decorations.requestApply ?? new Map();
@@ -156,14 +156,14 @@ internals.Server = class {
156156

157157
// Response
158158

159-
Hoek.assert(!this._core.Response.reserved.includes(property), 'Cannot override built-in response interface decoration:', propertyName);
159+
Hoek.assert(!this._core.Response.reserved.includes(property), 'Cannot override the built-in response interface decoration:', propertyName);
160160
this._core.Response.prototype[property] = method;
161161
}
162162
else if (type === 'toolkit') {
163163

164164
// Toolkit
165165

166-
Hoek.assert(!Toolkit.reserved.includes(property), 'Cannot override built-in toolkit decoration:', propertyName);
166+
Hoek.assert(!Toolkit.reserved.includes(property), 'Cannot override the built-in toolkit decoration:', propertyName);
167167
this._core.toolkit.decorate(property, method);
168168
}
169169
else {

lib/types/server/server.d.ts

+60-15
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,48 @@ import {
5353
import { ServerOptions } from './options';
5454
import { ServerState, ServerStateCookieOptions } from './state';
5555

56+
type ReservedRequestKeys = (
57+
'server' | 'url' | 'query' | 'path' | 'method' |
58+
'mime' | 'setUrl' | 'setMethod' | 'headers' | 'id' |
59+
'app' | 'plugins' | 'route' | 'auth' | 'pre' |
60+
'preResponses' | 'info' | 'isInjected' | 'orig' |
61+
'params' | 'paramsArray' | 'payload' | 'state' |
62+
'response' | 'raw' | 'domain' | 'log' | 'logs' |
63+
'generateResponse' |
64+
65+
// Private functions
66+
'_allowInternals' | '_closed' | '_core' |
67+
'_entity' | '_eventContext' | '_events' | '_expectContinue' |
68+
'_isInjected' | '_isPayloadPending' | '_isReplied' |
69+
'_route' | '_serverTimeoutId' | '_states' | '_url' |
70+
'_urlError' | '_initializeUrl' | '_setUrl' | '_parseUrl' |
71+
'_parseQuery'
72+
73+
);
74+
75+
type ReservedToolkitKeys = (
76+
'abandon' | 'authenticated' | 'close' | 'context' | 'continue' |
77+
'entity' | 'redirect' | 'realm' | 'request' | 'response' |
78+
'state' | 'unauthenticated' | 'unstate'
79+
);
80+
81+
type ReservedServerKeys = (
82+
// Public functions
83+
'app' | 'auth' | 'cache' | 'decorations' | 'events' | 'info' |
84+
'listener' | 'load' | 'methods' | 'mime' | 'plugins' | 'registrations' |
85+
'settings' | 'states' | 'type' | 'version' | 'realm' | 'control' | 'decoder' |
86+
'bind' | 'control' | 'decoder' | 'decorate' | 'dependency' | 'encoder' |
87+
'event' | 'expose' | 'ext' | 'inject' | 'log' | 'lookup' | 'match' | 'method' |
88+
'path' | 'register' | 'route' | 'rules' | 'state' | 'table' | 'validator' |
89+
'start' | 'initialize' | 'stop' |
90+
91+
// Private functions
92+
'_core' | '_initialize' | '_start' | '_stop' | '_cachePolicy' | '_createCache' |
93+
'_clone' | '_ext' | '_addRoute'
94+
);
95+
96+
type ExceptName<Property, ReservedKeys> = Property extends ReservedKeys ? never : Property;
97+
5698
/**
5799
* User-extensible type for application specific state (`server.app`).
58100
*/
@@ -309,21 +351,24 @@ export class Server<A = ServerApplicationState> {
309351
* @return void;
310352
* [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-serverdecoratetype-property-method-options)
311353
*/
312-
decorate(type: 'handler', property: DecorateName, method: HandlerDecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
313-
decorate(type: 'request', property: DecorateName, method: (existing: ((...args: any[]) => any)) => (request: Request) => DecorationMethod<Request>, options: {apply: true, extend: true}): void;
314-
decorate(type: 'request', property: DecorateName, method: (request: Request) => DecorationMethod<Request>, options: {apply: true, extend?: boolean | undefined}): void;
315-
decorate(type: 'request', property: DecorateName, method: DecorationMethod<Request>, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
316-
decorate(type: 'request', property: DecorateName, value: (existing: ((...args: any[]) => any)) => (request: Request) => any, options: {apply: true, extend: true}): void;
317-
decorate(type: 'request', property: DecorateName, value: (request: Request) => any, options: {apply: true, extend?: boolean | undefined}): void;
318-
decorate(type: 'request', property: DecorateName, value: any, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
319-
decorate(type: 'toolkit', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationMethod<ResponseToolkit>, options: {apply?: boolean | undefined, extend: true}): void;
320-
decorate(type: 'toolkit', property: DecorateName, method: DecorationMethod<ResponseToolkit>, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
321-
decorate(type: 'toolkit', property: DecorateName, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void;
322-
decorate(type: 'toolkit', property: DecorateName, value: any, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
323-
decorate(type: 'server', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationMethod<Server>, options: {apply?: boolean | undefined, extend: true}): void;
324-
decorate(type: 'server', property: DecorateName, method: DecorationMethod<Server>, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
325-
decorate(type: 'server', property: DecorateName, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void;
326-
decorate(type: 'server', property: DecorateName, value: any, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
354+
decorate <P extends DecorateName>(type: 'handler', property: P, method: HandlerDecorationMethod, options?: { apply?: boolean | undefined, extend?: never }): void;
355+
356+
decorate <P extends DecorateName>(type: 'request', property: ExceptName<P, ReservedRequestKeys>, method: (existing: ((...args: any[]) => any)) => (request: Request) => DecorationMethod<Request>, options: {apply: true, extend: true}): void;
357+
decorate <P extends DecorateName>(type: 'request', property: ExceptName<P, ReservedRequestKeys>, method: (request: Request) => DecorationMethod<Request>, options: {apply: true, extend?: boolean | undefined}): void;
358+
decorate <P extends DecorateName>(type: 'request', property: ExceptName<P, ReservedRequestKeys>, method: DecorationMethod<Request>, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
359+
decorate <P extends DecorateName>(type: 'request', property: ExceptName<P, ReservedRequestKeys>, value: (existing: ((...args: any[]) => any)) => (request: Request) => any, options: {apply: true, extend: true}): void;
360+
decorate <P extends DecorateName>(type: 'request', property: ExceptName<P, ReservedRequestKeys>, value: (request: Request) => any, options: {apply: true, extend?: boolean | undefined}): void;
361+
decorate <P extends DecorateName>(type: 'request', property: ExceptName<P, ReservedRequestKeys>, value: object, options?: never): void;
362+
363+
decorate <P extends DecorateName>(type: 'toolkit', property: ExceptName<P, ReservedToolkitKeys>, method: (existing: ((...args: any[]) => any)) => DecorationMethod<ResponseToolkit>, options: {apply?: boolean | undefined, extend: true}): void;
364+
decorate <P extends DecorateName>(type: 'toolkit', property: ExceptName<P, ReservedToolkitKeys>, method: DecorationMethod<ResponseToolkit>, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
365+
decorate <P extends DecorateName>(type: 'toolkit', property: ExceptName<P, ReservedToolkitKeys>, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void;
366+
decorate <P extends DecorateName>(type: 'toolkit', property: ExceptName<P, ReservedToolkitKeys>, value: object, options?: never): void;
367+
368+
decorate <P extends DecorateName>(type: 'server', property: ExceptName<P, ReservedServerKeys>, value: object, options?: never): void;
369+
decorate <P extends DecorateName>(type: 'server', property: ExceptName<P, ReservedServerKeys>, method: (existing: ((...args: any[]) => any)) => DecorationMethod<Server>, options: {apply?: boolean | undefined, extend: true}): void;
370+
decorate <P extends DecorateName>(type: 'server', property: ExceptName<P, ReservedServerKeys>, method: DecorationMethod<Server>, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void;
371+
decorate <P extends DecorateName>(type: 'server', property: ExceptName<P, ReservedServerKeys>, value: (existing: ((...args: any[]) => any)) => any, options: {apply?: boolean | undefined, extend: true}): void;
327372

328373
/**
329374
* Used within a plugin to declare a required dependency on other plugins where:

test/server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ describe('Server', () => {
592592
expect(() => {
593593

594594
server.decorate('toolkit', 'redirect', () => { });
595-
}).to.throw('Cannot override built-in toolkit decoration: redirect');
595+
}).to.throw('Cannot override the built-in toolkit decoration: redirect');
596596
});
597597

598598
it('decorates server', async () => {

test/types/index.ts

+93-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
Server,
1111
ServerRoute,
1212
server as createServer,
13-
ServerRegisterPluginObject
13+
ServerRegisterPluginObject,
14+
Lifecycle
1415
} from '../..';
1516

1617
const { expect: check } = lab;
@@ -123,4 +124,94 @@ server.method('test.add', (a: number, b: number) => a + b, {
123124
segment: 'test-segment',
124125
},
125126
generateKey: (a: number, b: number) => `${a}${b}`
126-
});
127+
});
128+
129+
declare module '../..' {
130+
interface Request {
131+
obj1: {
132+
func1(a: number, b: number): number;
133+
};
134+
135+
func2: (a: number, b: number) => number;
136+
}
137+
138+
interface ResponseToolkit {
139+
obj2: {
140+
func3(a: number, b: number): number;
141+
};
142+
143+
func4: (a: number, b: number) => number;
144+
}
145+
146+
interface Server {
147+
obj3: {
148+
func5(a: number, b: number): number;
149+
};
150+
151+
func6: (a: number, b: number) => number;
152+
}
153+
}
154+
155+
const theFunc = (a: number, b: number) => a + b;
156+
const theLifecycleMethod: Lifecycle.Method = () => 'ok';
157+
158+
// Error when decorating existing properties
159+
// @ts-expect-error Lab does not support overload errors
160+
check.error(() => server.decorate('request', 'payload', theFunc));
161+
// @ts-expect-error Lab does not support overload errors
162+
check.error(() => server.decorate('toolkit', 'state', theFunc));
163+
// @ts-expect-error Lab does not support overload errors
164+
check.error(() => server.decorate('server', 'dependency', theFunc));
165+
// @ts-expect-error Lab does not support overload errors
166+
check.error(() => server.decorate('server', 'dependency', theFunc));
167+
168+
server.decorate('handler', 'func1_1', () => theLifecycleMethod);
169+
server.decorate('handler', 'func1_2', () => theLifecycleMethod, { apply: true });
170+
171+
// Error when extending on handler
172+
// @ts-expect-error Lab does not support overload errors
173+
check.error(() => server.decorate('handler', 'func1_3', () => theLifecycleMethod, { apply: true, extend: true }));
174+
175+
// Error when handler does not return a lifecycle method
176+
// @ts-expect-error Lab does not support overload errors
177+
check.error(() => server.decorate('handler', 'func1_4', theFunc));
178+
179+
// Decorating request with functions
180+
server.decorate('request', 'func2_1', theFunc);
181+
server.decorate('request', 'func2_1', () => theFunc, { apply: true, extend: true });
182+
server.decorate('request', 'func2_2', theFunc, { apply: true });
183+
server.decorate('request', 'func2_2', theFunc, { extend: true });
184+
185+
// Decorating toolkit with functions
186+
server.decorate('toolkit', 'func4_1', theFunc);
187+
server.decorate('toolkit', 'func4_1', theFunc, { apply: true, extend: true });
188+
server.decorate('toolkit', 'func4_2', theFunc, { apply: true });
189+
server.decorate('toolkit', 'func4_2', theFunc, { extend: true });
190+
191+
// Decorating server with functions
192+
server.decorate('server', 'func6_1', theFunc);
193+
server.decorate('server', 'func6_1', theFunc, { apply: true, extend: true });
194+
server.decorate('server', 'func6_2', theFunc, { apply: true });
195+
server.decorate('server', 'func6_2', theFunc, { extend: true });
196+
197+
// Decorating request with objects
198+
server.decorate('request', 'obj1_1', { func1: theFunc });
199+
200+
// Type error when extending on request with objects
201+
// @ts-expect-error Lab does not support overload errors
202+
check.error(() => server.decorate('request', 'obj1_1', { func1: theFunc }, { apply: true, extend: true }));
203+
204+
205+
// Decorating toolkit with objects
206+
server.decorate('toolkit', 'obj2_1', { func3: theFunc });
207+
208+
// Error when extending on toolkit with objects
209+
// @ts-expect-error Lab does not support overload errors
210+
check.error(() => server.decorate('toolkit', 'obj2_1', { func3: theFunc }, { apply: true, extend: true }));
211+
212+
// Decorating server with objects
213+
server.decorate('server', 'obj3_1', { func5: theFunc });
214+
215+
// Error when extending on server with objects
216+
// @ts-expect-error Lab does not support overload errors
217+
check.error(() => server.decorate('server', 'obj3_1', { func5: theFunc }, { apply: true, extend: true }));

0 commit comments

Comments
 (0)