-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmod.ts
More file actions
130 lines (121 loc) · 3.35 KB
/
Copy pathmod.ts
File metadata and controls
130 lines (121 loc) · 3.35 KB
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
const cloneSymbol = Symbol.for("rc-dispose.clone");
const countSymbol = Symbol.for("rc-dispose.count");
export { cloneSymbol as clone, countSymbol as count };
type RcManagement<T> = {
/**
* This is dangerous.
*/
[countSymbol](n?: number): number;
[cloneSymbol](): Rc<T>;
};
/**
* Rc wrapped value.
*/
export type Rc<T> =
& T
& RcManagement<T>;
/**
* Extra options of {@link rc}.
*/
export interface RcOptions {
/** The initial count of rc. */
count?: number;
}
/**
* Wrap a disposable or async disposable value in RC,
* so that it have to be disposed multiple times to actually dispose it.
*
* @param value the value to wrap
* @param options extra options, see {@link RcOptions}
* @returns reference counted disposable or async disposable
*/
export function rc<T extends object, const O extends RcOptions>(
value: T,
options?: O,
): Rc<T> {
const validated = validateOptions(options);
const state = { ...validated };
const proxy = new Proxy(value, {
get(target, prop, receiver) {
switch (prop) {
// dispose
case Symbol.dispose: {
const dispose = Reflect.get(target, prop, receiver) as
| (() => void)
| undefined;
if (!dispose) return dispose;
return function () {
if (state.count > 0) {
state.count -= 1;
}
if (state.count == 0) {
return Reflect.apply(dispose, target, []);
}
};
}
// async dispose
case Symbol.asyncDispose: {
const asyncDispose = Reflect.get(
target,
prop,
receiver,
) as (() => Promise<void> | undefined);
if (!asyncDispose) return asyncDispose;
return function () {
if (state.count > 0) {
state.count -= 1;
}
if (state.count == 0) {
return Reflect.apply(asyncDispose, target, []);
}
};
}
// Symbol.for("rc-dispose.clone")
case cloneSymbol: {
return function (): Rc<T> {
state.count += 1;
return proxy;
};
}
// Symbol.for("rc-dispose.count")
case countSymbol: {
return function (n?: number | ((_: number) => number)): number {
// no n => getter
if (!n) return state.count;
if (n instanceof Function) {
const result = n(state.count);
if (!Number.isInteger(result) || result < 0) {
throw new RangeError("n must return non negative integer");
}
state.count = result;
return state.count;
} else {
if (!Number.isInteger(n) || n < 0) {
throw new RangeError("n must be non negative integer");
}
state.count = n;
return state.count;
}
};
}
// fallback to object
default: {
return Reflect.get(target, prop, receiver);
}
}
},
}) as Rc<T>;
return proxy;
}
/**
* Validate rc options.
*/
function validateOptions(options?: RcOptions): Required<RcOptions> {
const count = options?.count ?? 1;
if (!Number.isInteger(count) || count < 0) {
throw new RangeError("count must be non negative integer");
}
return {
count,
};
}