-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathscope-internal.ts
More file actions
121 lines (110 loc) · 3.66 KB
/
scope-internal.ts
File metadata and controls
121 lines (110 loc) · 3.66 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
import { Children, Priority } from "./contexts.ts";
import { Err, Ok, unbox } from "./result.ts";
import { createTask } from "./task.ts";
import type { Context, Operation, Scope, Task } from "./types.ts";
import { type WithResolvers, withResolvers } from "./with-resolvers.ts";
export function createScopeInternal(
parent?: Scope,
): [ScopeInternal, () => Operation<void>] {
let destructors = new Set<() => Operation<void>>();
let finalizer: (() => Operation<void>) | undefined = undefined;
let contexts: Record<string, unknown> = Object.create(
parent ? (parent as ScopeInternal).contexts : null,
);
let scope: ScopeInternal = Object.create({
[Symbol.toStringTag]: "Scope",
contexts,
get<T>(context: Context<T>): T | undefined {
return (contexts[context.name] ?? context.defaultValue) as T | undefined;
},
set<T>(context: Context<T>, value: T): T {
return contexts[context.name] = value;
},
expect<T>(context: Context<T>): T {
let value = scope.get(context);
if (typeof value === "undefined") {
let error = new Error(context.name);
error.name = `MissingContextError`;
throw error;
}
return value;
},
delete<T>(context: Context<T>): boolean {
return delete contexts[context.name];
},
hasOwn<T>(context: Context<T>): boolean {
return !!Reflect.getOwnPropertyDescriptor(contexts, context.name);
},
run<T>(operation: () => Operation<T>): Task<T> {
let { task, start } = createTask({ operation, owner: scope });
start();
return task;
},
spawn<T>(operation: () => Operation<T>): Operation<Task<T>> {
return {
*[Symbol.iterator]() {
let { task, start } = createTask({ operation, owner: scope });
start();
return task;
},
};
},
ensure(op: () => Operation<void>): () => void {
destructors.add(op);
return () => destructors.delete(op);
},
// Set a finalizer that runs BEFORE regular destructors.
// Used by tasks to close their delimiter before child tasks are destroyed.
setFinalizer(op: () => Operation<void>): void {
finalizer = op;
},
});
scope.set(Priority, scope.expect(Priority) + 1);
scope.set(Children, new Set());
parent?.expect(Children).add(scope);
let unbind = parent ? (parent as ScopeInternal).ensure(destroy) : () => {};
let destruction: WithResolvers<void> | undefined = undefined;
function* destroy(): Operation<void> {
if (destruction) {
return yield* destruction.operation;
}
destruction = withResolvers<void>();
parent?.expect(Children).delete(scope);
unbind();
let outcome = Ok();
try {
// Run the finalizer first (e.g., close the task's delimiter).
// This ensures the task's finally block runs while children are still alive.
if (finalizer) {
try {
yield* finalizer();
} catch (error) {
outcome = Err(error as Error);
}
finalizer = undefined;
}
// Then run regular destructors in reverse order (children first)
for (let destructor of [...destructors].reverse()) {
try {
destructors.delete(destructor);
yield* destructor();
} catch (error) {
outcome = Err(error as Error);
}
}
} finally {
if (outcome.ok) {
destruction.resolve();
} else {
destruction.reject(outcome.error);
}
}
unbox(outcome);
}
return [scope, destroy];
}
export interface ScopeInternal extends Scope {
contexts: Record<string, unknown>;
ensure(op: () => Operation<void>): () => void;
setFinalizer(op: () => Operation<void>): void;
}