From 5045671d32c8a8fde99bf53170da36fbf7d67c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Jan 2022 15:23:37 +0100 Subject: [PATCH] remove the middleware part of context composition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/backend-common/api-report.md | 18 ++++---- .../src/context/AbortContext.test.ts | 12 ----- .../src/context/AbortContext.ts | 6 +-- .../src/context/Contexts.test.ts | 13 +++--- .../backend-common/src/context/Contexts.ts | 46 ++++++++++--------- .../src/context/RootContext.test.ts | 11 +---- .../backend-common/src/context/RootContext.ts | 25 +++++++--- .../src/context/ValueContext.test.ts | 11 ----- .../src/context/ValueContext.ts | 6 +-- packages/backend-common/src/context/index.ts | 2 +- packages/backend-common/src/context/types.ts | 20 -------- 11 files changed, 63 insertions(+), 107 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index e22195c2b3..478a17d1fd 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -151,23 +151,23 @@ export interface ContainerRunner { export interface Context { readonly abortSignal: AbortSignal_2; readonly deadline: Date | undefined; - use(...decorators: ContextDecorator[]): Context; value(key: string): T | undefined; } -// @public -export type ContextDecorator = (ctx: Context) => Context; - // @public export class Contexts { static root(): Context; - static setAbort(source: AbortController_2 | AbortSignal_2): ContextDecorator; - static setTimeoutDuration(timeout: Duration): ContextDecorator; - static setTimeoutMillis(timeout: number): ContextDecorator; - static setValue( + static withAbort( + parentCtx: Context, + source: AbortController_2 | AbortSignal_2, + ): Context; + static withTimeoutDuration(parentCtx: Context, timeout: Duration): Context; + static withTimeoutMillis(parentCtx: Context, timeout: number): Context; + static withValue( + parentCtx: Context, key: string, value: unknown | ((previous: unknown | undefined) => unknown), - ): ContextDecorator; + ): Context; } // @public @deprecated diff --git a/packages/backend-common/src/context/AbortContext.test.ts b/packages/backend-common/src/context/AbortContext.test.ts index aa01c2209a..f47e1d4beb 100644 --- a/packages/backend-common/src/context/AbortContext.test.ts +++ b/packages/backend-common/src/context/AbortContext.test.ts @@ -16,7 +16,6 @@ import { AbortController } from 'node-abort-controller'; import { AbortContext } from './AbortContext'; -import { Contexts } from './Contexts'; import { RootContext } from './RootContext'; describe('AbortContext', () => { @@ -343,15 +342,4 @@ describe('AbortContext', () => { expect(childListener).toBeCalledTimes(0); }); }); - - it('can decorate', () => { - const root = new RootContext(); - const controller = new AbortController(); - const parent = AbortContext.forSignal(root, controller.signal); - const child = parent.use( - Contexts.setValue('a', 2), - Contexts.setValue('a', 3), - ); - expect(child.value('a')).toBe(3); - }); }); diff --git a/packages/backend-common/src/context/AbortContext.ts b/packages/backend-common/src/context/AbortContext.ts index 86069d290b..8a119358a6 100644 --- a/packages/backend-common/src/context/AbortContext.ts +++ b/packages/backend-common/src/context/AbortContext.ts @@ -15,7 +15,7 @@ */ import { AbortController, AbortSignal } from 'node-abort-controller'; -import { Context, ContextDecorator } from './types'; +import { Context } from './types'; /** * A context that implements various abort related functionality. @@ -130,8 +130,4 @@ export class AbortContext implements Context { value(key: string): T | undefined { return this.parent.value(key); } - - use(...items: ContextDecorator[]): Context { - return items.reduce((prev, curr) => curr(prev), this as Context); - } } diff --git a/packages/backend-common/src/context/Contexts.test.ts b/packages/backend-common/src/context/Contexts.test.ts index 6c05319a4f..cbd61d20a9 100644 --- a/packages/backend-common/src/context/Contexts.test.ts +++ b/packages/backend-common/src/context/Contexts.test.ts @@ -35,7 +35,7 @@ describe('Contexts', () => { it('works for controllers', () => { const controller = new AbortController(); const parent = Contexts.root(); - const child = parent.use(Contexts.setAbort(controller)); + const child = Contexts.withAbort(parent, controller); expect(child.abortSignal.aborted).toBe(false); controller.abort(); expect(child.abortSignal.aborted).toBe(true); @@ -44,7 +44,7 @@ describe('Contexts', () => { it('works for signals', () => { const controller = new AbortController(); const parent = Contexts.root(); - const child = parent.use(Contexts.setAbort(controller.signal)); + const child = Contexts.withAbort(parent, controller.signal); expect(child.abortSignal.aborted).toBe(false); controller.abort(); expect(child.abortSignal.aborted).toBe(true); @@ -55,8 +55,9 @@ describe('Contexts', () => { it('works', () => { jest.useFakeTimers(); const parent = Contexts.root(); - const child = parent.use( - Contexts.setTimeoutDuration(Duration.fromMillis(200)), + const child = Contexts.withTimeoutDuration( + parent, + Duration.fromMillis(200), ); expect(child.abortSignal.aborted).toBe(false); jest.advanceTimersByTime(100); @@ -70,7 +71,7 @@ describe('Contexts', () => { it('works', () => { jest.useFakeTimers(); const parent = Contexts.root(); - const child = parent.use(Contexts.setTimeoutMillis(200)); + const child = Contexts.withTimeoutMillis(parent, 200); expect(child.abortSignal.aborted).toBe(false); jest.advanceTimersByTime(100); expect(child.abortSignal.aborted).toBe(false); @@ -82,7 +83,7 @@ describe('Contexts', () => { describe('setValue', () => { it('works', () => { const parent = Contexts.root(); - const child = parent.use(Contexts.setValue('k', 'v')); + const child = Contexts.withValue(parent, 'k', 'v'); expect(child.value('k')).toBe('v'); }); }); diff --git a/packages/backend-common/src/context/Contexts.ts b/packages/backend-common/src/context/Contexts.ts index 692248028a..8bb086168a 100644 --- a/packages/backend-common/src/context/Contexts.ts +++ b/packages/backend-common/src/context/Contexts.ts @@ -18,7 +18,7 @@ import { Duration } from 'luxon'; import { AbortController, AbortSignal } from 'node-abort-controller'; import { AbortContext } from './AbortContext'; import { RootContext } from './RootContext'; -import { Context, ContextDecorator } from './types'; +import { Context } from './types'; import { ValueContext } from './ValueContext'; /** @@ -51,15 +51,18 @@ export class Contexts { * If the given source was already aborted, then a new already-aborted context * is returned. * + * @param parentCtx - A parent context that shall be used as a base * @param source - An abort controller or signal that you intend to perhaps * trigger at some later point in time. - * @returns A decorator that can be passed to {@link Context.use} + * @returns A new {@link Context} */ - static setAbort(source: AbortController | AbortSignal): ContextDecorator { - return ctx => - 'aborted' in source - ? AbortContext.forSignal(ctx, source) - : AbortContext.forController(ctx, source); + static withAbort( + parentCtx: Context, + source: AbortController | AbortSignal, + ): Context { + return 'aborted' in source + ? AbortContext.forSignal(parentCtx, source) + : AbortContext.forController(parentCtx, source); } /** @@ -67,13 +70,13 @@ export class Contexts { * any parent context signals, or when the given amount of time has passed. * This may affect the deadline. * + * @param parentCtx - A parent context that shall be used as a base * @param timeout - The duration of time, after which the derived context will * signal to abort. - * @returns A decorator that can be passed to {@link Context.use} + * @returns A new {@link Context} */ - static setTimeoutDuration(timeout: Duration): ContextDecorator { - return ctx => - AbortContext.forTimeoutMillis(ctx, timeout.as('milliseconds')); + static withTimeoutDuration(parentCtx: Context, timeout: Duration): Context { + return AbortContext.forTimeoutMillis(parentCtx, timeout.as('milliseconds')); } /** @@ -81,30 +84,31 @@ export class Contexts { * any parent context signals, or when the given amount of time has passed. * This may affect the deadline. * + * @param parentCtx - A parent context that shall be used as a base * @param timeout - The number of milliseconds, after which the derived * context will signal to abort. - * @returns A decorator that can be passed to {@link Context.use} + * @returns A new {@link Context} */ - static setTimeoutMillis(timeout: number): ContextDecorator { - return ctx => AbortContext.forTimeoutMillis(ctx, timeout); + static withTimeoutMillis(parentCtx: Context, timeout: number): Context { + return AbortContext.forTimeoutMillis(parentCtx, timeout); } /** * Creates a derived context, which has a specific key-value pair set as well * as all key-value pairs that were set in the original context. * + * @param parentCtx - A parent context that shall be used as a base * @param key - The key of the value to set * @param value - The value, or a function that accepts the previous value (or * undefined if not set yet) and computes the new value - * @returns A decorator that can be passed to {@link Context.use} + * @returns A new {@link Context} */ - static setValue( + static withValue( + parentCtx: Context, key: string, value: unknown | ((previous: unknown | undefined) => unknown), - ): ContextDecorator { - return ctx => { - const v = typeof value === 'function' ? value(ctx.value(key)) : value; - return ValueContext.forConstantValue(ctx, key, v); - }; + ): Context { + const v = typeof value === 'function' ? value(parentCtx.value(key)) : value; + return ValueContext.forConstantValue(parentCtx, key, v); } } diff --git a/packages/backend-common/src/context/RootContext.test.ts b/packages/backend-common/src/context/RootContext.test.ts index 28a3af0a52..395786e3dc 100644 --- a/packages/backend-common/src/context/RootContext.test.ts +++ b/packages/backend-common/src/context/RootContext.test.ts @@ -14,23 +14,14 @@ * limitations under the License. */ -import { Contexts } from './Contexts'; import { RootContext } from './RootContext'; describe('RootContext', () => { it('returns empty values', async () => { const ctx = new RootContext(); expect(ctx.abortSignal).toBeDefined(); + expect(ctx.abortSignal.aborted).toBe(false); expect(ctx.deadline).toBeUndefined(); expect(ctx.value('a')).toBeUndefined(); }); - - it('can decorate', () => { - const parent = new RootContext(); - const child = parent.use( - Contexts.setValue('a', 2), - Contexts.setValue('a', 3), - ); - expect(child.value('a')).toBe(3); - }); }); diff --git a/packages/backend-common/src/context/RootContext.ts b/packages/backend-common/src/context/RootContext.ts index 96f55dd76a..61962380a4 100644 --- a/packages/backend-common/src/context/RootContext.ts +++ b/packages/backend-common/src/context/RootContext.ts @@ -14,21 +14,32 @@ * limitations under the License. */ -import { AbortController } from 'node-abort-controller'; -import { Context, ContextDecorator } from './types'; +import { AbortSignal } from 'node-abort-controller'; +import { Context } from './types'; + +/** + * Since the root context can never abort, and since nobody is every meant to + * dispatch events through it, we can use a static dummy instance for + * efficiency. + */ +const dummyAbortSignal: AbortSignal = Object.freeze({ + aborted: false, + addEventListener() {}, + removeEventListener() {}, + dispatchEvent() { + return true; + }, + onabort: null, +}); /** * An empty root context. */ export class RootContext implements Context { - readonly abortSignal = new AbortController().signal; + readonly abortSignal = dummyAbortSignal; readonly deadline = undefined; value(_key: string): T | undefined { return undefined; } - - use(...items: ContextDecorator[]): Context { - return items.reduce((prev, curr) => curr(prev), this as Context); - } } diff --git a/packages/backend-common/src/context/ValueContext.test.ts b/packages/backend-common/src/context/ValueContext.test.ts index 792893833d..6a216d4548 100644 --- a/packages/backend-common/src/context/ValueContext.test.ts +++ b/packages/backend-common/src/context/ValueContext.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Contexts } from './Contexts'; import { RootContext } from './RootContext'; import { ValueContext } from './ValueContext'; @@ -42,14 +41,4 @@ describe('ValueContext', () => { expect(d.value('b')).toBe(4); expect(d.value('x')).toBe(2); }); - - it('can decorate', () => { - const root = new RootContext(); - const parent = ValueContext.forConstantValue(root, 'a', 1); - const child = parent.use( - Contexts.setValue('a', 2), - Contexts.setValue('a', 3), - ); - expect(child.value('a')).toBe(3); - }); }); diff --git a/packages/backend-common/src/context/ValueContext.ts b/packages/backend-common/src/context/ValueContext.ts index 789741e45f..441aaec984 100644 --- a/packages/backend-common/src/context/ValueContext.ts +++ b/packages/backend-common/src/context/ValueContext.ts @@ -15,7 +15,7 @@ */ import { AbortSignal } from 'node-abort-controller'; -import { Context, ContextDecorator } from './types'; +import { Context } from './types'; /** * A context that just holds a single value, and delegates the rest to its @@ -43,8 +43,4 @@ export class ValueContext implements Context { value(key: string): T | undefined { return key === this._key ? (this._value as T) : this._parent.value(key); } - - use(...items: ContextDecorator[]): Context { - return items.reduce((prev, curr) => curr(prev), this as Context); - } } diff --git a/packages/backend-common/src/context/index.ts b/packages/backend-common/src/context/index.ts index 64fe635de9..37a6e29c8c 100644 --- a/packages/backend-common/src/context/index.ts +++ b/packages/backend-common/src/context/index.ts @@ -15,4 +15,4 @@ */ export { Contexts } from './Contexts'; -export type { Context, ContextDecorator } from './types'; +export type { Context } from './types'; diff --git a/packages/backend-common/src/context/types.ts b/packages/backend-common/src/context/types.ts index 25a6d2354b..57bf836560 100644 --- a/packages/backend-common/src/context/types.ts +++ b/packages/backend-common/src/context/types.ts @@ -16,14 +16,6 @@ import { AbortSignal } from 'node-abort-controller'; -/** - * A function that accepts a context and produces a new, derived context from, - * decorated with some specific behavior. - * - * @public - */ -export type ContextDecorator = (ctx: Context) => Context; - /** * A context that is meant to be passed as a ctx variable down the call chain, * to pass along scoped information and abort signals. @@ -50,16 +42,4 @@ export interface Context { * @returns The associated value, or undefined if not set */ value(key: string): T | undefined; - - /** - * Decorates this context with one or more behaviors. - * - * @remarks - * - * The decorators are applied in the order that they are given. - * - * @param decorators - The decorators to apply - * @returns A derived context with the relevant behaviors - */ - use(...decorators: ContextDecorator[]): Context; }