Skip to content
This repository was archived by the owner on Sep 17, 2023. It is now read-only.

Commit 113e17a

Browse files
committed
Implement FnContextKey
1 parent 6fd85fd commit 113e17a

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

src/fn-context-key.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ContextKeyError } from './context-key-error';
2+
import { ContextRegistry } from './context-registry';
3+
import { ContextValues } from './context-values';
4+
import { FnContextKey } from './fn-context-key';
5+
6+
describe('FnContextKey', () => {
7+
8+
let registry: ContextRegistry<ContextValues>;
9+
let values: ContextValues;
10+
let key: FnContextKey<[string], number>;
11+
12+
beforeEach(() => {
13+
registry = new ContextRegistry();
14+
values = registry.newValues();
15+
key = new FnContextKey('test-fn');
16+
});
17+
18+
it('delegates to function', () => {
19+
registry.provide({ a: key, is: value => value.length });
20+
expect(values.get(key)('some')).toEqual(4);
21+
});
22+
it('delegates to updated function', () => {
23+
registry.provide({ a: key, is: value => value.length });
24+
expect(values.get(key)('some')).toEqual(4);
25+
registry.provide({ a: key, is: value => value.length + 100 });
26+
expect(values.get(key)('some')).toEqual(104);
27+
});
28+
it('delegates to fallback function when absent', () => {
29+
expect(values.get(key, { or: value => value.length })('some')).toEqual(4);
30+
});
31+
it('does not throw when no function to delegate', () => {
32+
expect(values.get(key)).toBeInstanceOf(Function);
33+
});
34+
it('throws when absent delegate called', () => {
35+
expect(() => values.get(key)('some')).toThrow(ContextKeyError);
36+
});
37+
it('throws when absent delegate called with `null` fallback', () => {
38+
expect(() => values.get(key, { or: null })!('some')).toThrow(ContextKeyError);
39+
});
40+
});

src/fn-context-key.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { AfterEvent, EventKeeper } from 'fun-events';
2+
import { ContextSeedKey, ContextValueOpts } from './context-key';
3+
import { ContextKeyError } from './context-key-error';
4+
import { ContextRef } from './context-ref';
5+
import { ContextUpKey } from './context-up-key';
6+
import { ContextValues } from './context-values';
7+
8+
/**
9+
* A reference to updatable context function value.
10+
*
11+
* @typeparam Args Function arguments tuple type.
12+
* @typeparam Ret Function return value type.
13+
* @typeparam Seed Value seed type.
14+
*/
15+
export type FnContextRef<Args extends any[], Ret = void, Seed = unknown> = ContextRef<
16+
(this: void, ...args: Args) => Ret,
17+
((this: void, ...args: Args) => Ret) | EventKeeper<((this: void, ...args: Args) => Ret)[]>,
18+
Seed>;
19+
20+
/**
21+
* A key of updatable context function value.
22+
*
23+
* The value associated with this key is a function that delegates to the last provided function. The target function
24+
* may be updated.
25+
*
26+
* The value is always present. But if the function to delegate is not provided, and no default/fallback function
27+
* provided, an attempt to call the delegate would throw an [[ContextKeyError]].
28+
*
29+
* It is an error to provide a `null` or `undefined` {@link ContextRequest.Opts.or fallback value} when requesting
30+
* an associated value. Use an `afterEventOf()` result as a fallback instead.
31+
*
32+
* @typeparam Args Function arguments tuple type.
33+
* @typeparam Ret Function return value type.
34+
*/
35+
export class FnContextKey<Args extends any[], Ret = void>
36+
extends ContextUpKey<(this: void, ...args: Args) => Ret, (this: void, ...args: Args) => Ret>
37+
implements FnContextRef<Args, Ret, AfterEvent<((this: void, ...args: Args) => Ret)[]>> {
38+
39+
/**
40+
* A function that will be called unless the function or fallback provided.
41+
*/
42+
readonly byDefault: (this: void, ...args: Args) => Ret;
43+
44+
/**
45+
* Constructs updatable context function key.
46+
*
47+
* @param name Human-readable key name.
48+
* @param seedKey Value seed key. A new one will be constructed when omitted.
49+
* @param byDefault The default function to call. If unspecified then the default function would raise an error.
50+
*/
51+
constructor(
52+
name: string,
53+
{
54+
seedKey,
55+
byDefault = () => { throw new ContextKeyError(this); },
56+
}: {
57+
seedKey?: ContextSeedKey<
58+
((this: void, ...args: Args) => Ret) | EventKeeper<((this: void, ...args: Args) => Ret)[]>,
59+
AfterEvent<((this: void, ...args: Args) => Ret)[]>>,
60+
byDefault?: (this: void, ...args: Args) => Ret,
61+
} = {},
62+
) {
63+
super(name, seedKey);
64+
this.byDefault = byDefault;
65+
}
66+
67+
grow<Ctx extends ContextValues>(
68+
opts: ContextValueOpts<
69+
Ctx,
70+
(this: void, ...args: Args) => Ret,
71+
EventKeeper<((this: void, ...args: Args) => Ret)[]> | ((this: void, ...args: Args) => Ret),
72+
AfterEvent<((this: void, ...args: Args) => Ret)[]>>,
73+
): (this: void, ...args: Args) => Ret {
74+
75+
let delegated!: (this: void, ...args: Args) => Ret;
76+
77+
opts.seed.consume((...fns) => {
78+
if (fns.length) {
79+
delegated = fns[fns.length - 1];
80+
} else {
81+
82+
const fallback = opts.byDefault(() => this.byDefault);
83+
84+
delegated = fallback || this.byDefault;
85+
}
86+
});
87+
88+
return (...args) => delegated(...args);
89+
}
90+
91+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export * from './context-seeder';
66
export * from './context-up-key';
77
export * from './context-value-spec';
88
export * from './context-values';
9+
export * from './fn-context-key';
910
export * from './simple-context-key';

0 commit comments

Comments
 (0)