-
-
Notifications
You must be signed in to change notification settings - Fork 85
/
context.ts
76 lines (64 loc) · 1.85 KB
/
context.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import assign from 'assign';
import defaultTo from 'defaultTo';
import invariant from 'invariant';
import optionalFunctionValue from 'optionalFunctionValue';
// eslint-disable-next-line max-lines-per-function
export function createContext<T extends Record<string, unknown>>(
init?: (ctxRef: Partial<T>, parentContext: T | void) => T | null
): {
run: <R>(ctxRef: Partial<T>, fn: (context: T) => R) => R;
bind: <Fn extends (...args: any[]) => any>(ctxRef: Partial<T>, fn: Fn) => Fn;
use: () => T | undefined;
useX: (errorMessage?: string) => T;
} {
const storage: { ctx?: T; ancestry: T[] } = { ancestry: [] };
return {
bind,
run,
use,
useX,
};
function useX(errorMessage?: string): T {
invariant(
storage.ctx,
defaultTo(errorMessage, 'Context was used after it was closed')
);
return storage.ctx as T;
}
function run<R>(ctxRef: Partial<T>, fn: (context: T) => R): R {
const parentContext = use();
const out = assign(
{},
parentContext ? parentContext : {},
optionalFunctionValue(init, ctxRef, parentContext) ?? ctxRef
) as T;
const ctx = set(Object.freeze(out));
storage.ancestry.unshift(ctx);
const res = fn(ctx);
clear();
return res;
}
function bind<Fn extends (...args: any[]) => any>(
ctxRef: Partial<T>,
fn: Fn
) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - this one's pretty hard to get right
const returnedFn: Fn = function (...runTimeArgs: Parameters<Fn>) {
return run<ReturnType<Fn>>(ctxRef, function () {
return fn(...runTimeArgs);
});
};
return returnedFn;
}
function use() {
return storage.ctx;
}
function set(value: T): T {
return (storage.ctx = value);
}
function clear() {
storage.ancestry.shift();
set(storage.ancestry[0] ?? null);
}
}