@agent-glue/glue 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-format.log +5 -7
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/effects.js +120 -13
- package/dist/effects.test.js +558 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/effects.test.ts +681 -0
- package/src/effects.ts +154 -13
package/src/effects.ts
CHANGED
|
@@ -5,55 +5,178 @@ function defaultDiscriminator<T extends Message>(m: Message): m is T {
|
|
|
5
5
|
return m === m;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export interface EffectOptions {
|
|
9
|
+
/** Timeout in ms. Rejects with TimeoutError (take) or idle timeout (generators). */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** AbortSignal. Rejects with AbortError when aborted. */
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TimeoutError extends Error {
|
|
16
|
+
constructor(message = "Operation timed out") {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "TimeoutError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class AbortError extends Error {
|
|
23
|
+
constructor(message = "Operation aborted") {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "AbortError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function wrapAbortReason(reason: unknown): string {
|
|
30
|
+
return typeof reason === "string" ? reason : "Operation aborted";
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
export function take<T extends Events, Events extends Message>(
|
|
9
34
|
actor: ActorInterface<Message, Events>,
|
|
10
|
-
discriminator: MessageDiscriminator<T> = defaultDiscriminator<T
|
|
35
|
+
discriminator: MessageDiscriminator<T> = defaultDiscriminator<T>,
|
|
36
|
+
options?: EffectOptions,
|
|
11
37
|
): Promise<T> {
|
|
12
|
-
return new Promise((resolve) => {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
let settled = false;
|
|
40
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
41
|
+
|
|
42
|
+
const cleanup = () => {
|
|
43
|
+
settled = true;
|
|
44
|
+
actor.disconnect(handler);
|
|
45
|
+
if (timer) clearTimeout(timer);
|
|
46
|
+
};
|
|
47
|
+
|
|
13
48
|
const handler = (message: Message) => {
|
|
49
|
+
if (settled) return;
|
|
14
50
|
if (discriminator(message)) {
|
|
15
|
-
|
|
51
|
+
cleanup();
|
|
16
52
|
resolve(message);
|
|
17
53
|
}
|
|
18
54
|
};
|
|
55
|
+
|
|
56
|
+
// AbortSignal - check before connecting
|
|
57
|
+
if (options?.signal) {
|
|
58
|
+
if (options.signal.aborted) {
|
|
59
|
+
reject(new AbortError(wrapAbortReason(options.signal.reason)));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
options.signal.addEventListener(
|
|
63
|
+
"abort",
|
|
64
|
+
() => {
|
|
65
|
+
if (!settled) {
|
|
66
|
+
cleanup();
|
|
67
|
+
reject(new AbortError(wrapAbortReason(options.signal!.reason)));
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{ once: true },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Timeout
|
|
75
|
+
if (options?.timeout != null) {
|
|
76
|
+
timer = setTimeout(() => {
|
|
77
|
+
if (!settled) {
|
|
78
|
+
cleanup();
|
|
79
|
+
reject(new TimeoutError());
|
|
80
|
+
}
|
|
81
|
+
}, options.timeout);
|
|
82
|
+
timer.unref();
|
|
83
|
+
}
|
|
84
|
+
|
|
19
85
|
actor.connect(handler);
|
|
20
86
|
});
|
|
21
87
|
}
|
|
22
88
|
|
|
23
89
|
export async function* takeEvery<Events extends Message>(
|
|
24
|
-
actor: ActorInterface<Message, Events
|
|
90
|
+
actor: ActorInterface<Message, Events>,
|
|
91
|
+
options?: EffectOptions,
|
|
25
92
|
): AsyncGenerator<Events> {
|
|
26
93
|
const eventQueue: Events[] = [];
|
|
27
94
|
let resolve: ((value: Events) => void) | null = null;
|
|
95
|
+
let reject: ((reason: Error) => void) | null = null;
|
|
96
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
97
|
+
|
|
98
|
+
const clearTimer = () => {
|
|
99
|
+
if (timer) {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
timer = undefined;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const resetTimer = () => {
|
|
106
|
+
clearTimer();
|
|
107
|
+
if (options?.timeout != null) {
|
|
108
|
+
timer = setTimeout(() => {
|
|
109
|
+
if (reject) {
|
|
110
|
+
const rej = reject;
|
|
111
|
+
reject = null;
|
|
112
|
+
resolve = null;
|
|
113
|
+
rej(new TimeoutError());
|
|
114
|
+
}
|
|
115
|
+
}, options.timeout);
|
|
116
|
+
timer.unref();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
28
119
|
|
|
29
120
|
const disconnect = actor.connect((message: Events) => {
|
|
30
121
|
if (resolve) {
|
|
31
|
-
resolve
|
|
122
|
+
const res = resolve;
|
|
32
123
|
resolve = null;
|
|
124
|
+
reject = null;
|
|
125
|
+
res(message);
|
|
33
126
|
} else {
|
|
34
127
|
eventQueue.push(message);
|
|
35
128
|
}
|
|
36
129
|
});
|
|
130
|
+
|
|
131
|
+
// AbortSignal
|
|
132
|
+
const onAbort = () => {
|
|
133
|
+
if (reject) {
|
|
134
|
+
const rej = reject;
|
|
135
|
+
reject = null;
|
|
136
|
+
resolve = null;
|
|
137
|
+
rej(new AbortError(wrapAbortReason(options?.signal?.reason)));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (options?.signal) {
|
|
142
|
+
if (options.signal.aborted) {
|
|
143
|
+
disconnect();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
37
149
|
try {
|
|
38
150
|
while (true) {
|
|
151
|
+
if (options?.signal?.aborted) break;
|
|
152
|
+
|
|
39
153
|
if (eventQueue.length > 0) {
|
|
40
|
-
|
|
154
|
+
const event = eventQueue.shift()!;
|
|
155
|
+
resetTimer(); // reset idle timer on event
|
|
156
|
+
yield event;
|
|
41
157
|
} else {
|
|
42
|
-
|
|
158
|
+
resetTimer(); // start idle timer while waiting
|
|
159
|
+
const event = await new Promise<Events>((res, rej) => {
|
|
43
160
|
resolve = res;
|
|
161
|
+
reject = rej;
|
|
44
162
|
});
|
|
163
|
+
clearTimer(); // event arrived, clear timer
|
|
164
|
+
yield event;
|
|
45
165
|
}
|
|
46
166
|
}
|
|
47
167
|
} finally {
|
|
168
|
+
clearTimer();
|
|
169
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
48
170
|
disconnect();
|
|
49
171
|
}
|
|
50
172
|
}
|
|
51
173
|
|
|
52
174
|
export async function* takeIf<T extends Events, Events extends Message>(
|
|
53
175
|
actor: ActorInterface<Message, Events>,
|
|
54
|
-
discriminator: MessageDiscriminator<T
|
|
176
|
+
discriminator: MessageDiscriminator<T>,
|
|
177
|
+
options?: EffectOptions,
|
|
55
178
|
): AsyncGenerator<T> {
|
|
56
|
-
for await (const event of takeEvery(actor)) {
|
|
179
|
+
for await (const event of takeEvery(actor, options)) {
|
|
57
180
|
if (discriminator(event)) {
|
|
58
181
|
yield event;
|
|
59
182
|
}
|
|
@@ -63,9 +186,10 @@ export async function* takeIf<T extends Events, Events extends Message>(
|
|
|
63
186
|
export async function* takeUntil<T extends Events, Events extends Message>(
|
|
64
187
|
actor: ActorInterface<Message, Events>,
|
|
65
188
|
discriminator: MessageDiscriminator<T>,
|
|
66
|
-
endCondition: (message: Events) => boolean = () => true
|
|
189
|
+
endCondition: (message: Events) => boolean = () => true,
|
|
190
|
+
options?: EffectOptions,
|
|
67
191
|
): AsyncGenerator<T> {
|
|
68
|
-
for await (const event of takeEvery(actor)) {
|
|
192
|
+
for await (const event of takeEvery(actor, options)) {
|
|
69
193
|
if (endCondition(event)) {
|
|
70
194
|
break;
|
|
71
195
|
}
|
|
@@ -78,11 +202,28 @@ export async function* takeUntil<T extends Events, Events extends Message>(
|
|
|
78
202
|
export function on<T extends Events, Events extends Message>(
|
|
79
203
|
actor: ActorInterface<Message, Events>,
|
|
80
204
|
discriminator: MessageDiscriminator<T>,
|
|
81
|
-
handler: (message: T) => void
|
|
205
|
+
handler: (message: T) => void,
|
|
206
|
+
options?: { signal?: AbortSignal },
|
|
82
207
|
): () => void {
|
|
83
|
-
|
|
208
|
+
if (options?.signal?.aborted) {
|
|
209
|
+
return () => {};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const disconnect = actor.connect((message) => {
|
|
84
213
|
if (discriminator(message)) {
|
|
85
214
|
handler(message);
|
|
86
215
|
}
|
|
87
216
|
});
|
|
217
|
+
|
|
218
|
+
if (options?.signal) {
|
|
219
|
+
options.signal.addEventListener(
|
|
220
|
+
"abort",
|
|
221
|
+
() => {
|
|
222
|
+
disconnect();
|
|
223
|
+
},
|
|
224
|
+
{ once: true },
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return disconnect;
|
|
88
229
|
}
|