@moeru/eventa 1.0.0-alpha.9 → 1.0.0-beta.1
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/README.md +238 -1
- package/dist/adapters/broadcast-channel/index.d.mts +2 -2
- package/dist/adapters/broadcast-channel/index.mjs +2 -2
- package/dist/adapters/electron/main.d.mts +10 -3
- package/dist/adapters/electron/main.mjs +3 -3
- package/dist/adapters/electron/main.mjs.map +1 -1
- package/dist/adapters/electron/renderer.d.mts +2 -2
- package/dist/adapters/electron/renderer.mjs +3 -3
- package/dist/adapters/event-emitter/index.d.mts +2 -2
- package/dist/adapters/event-emitter/index.mjs +2 -2
- package/dist/adapters/event-target/index.d.mts +2 -2
- package/dist/adapters/event-target/index.mjs +2 -2
- package/dist/adapters/websocket/h3/index.d.mts +2 -2
- package/dist/adapters/websocket/h3/index.mjs +2 -2
- package/dist/adapters/websocket/h3/index.mjs.map +1 -1
- package/dist/adapters/websocket/index.d.mts +1 -1
- package/dist/adapters/websocket/native/index.d.mts +2 -2
- package/dist/adapters/websocket/native/index.mjs +2 -2
- package/dist/adapters/webworkers/index.d.mts +4 -43
- package/dist/adapters/webworkers/index.mjs +5 -4
- package/dist/adapters/webworkers/index.mjs.map +1 -1
- package/dist/adapters/webworkers/worker/index.d.mts +2 -1
- package/dist/adapters/webworkers/worker/index.mjs +4 -3
- package/dist/adapters/webworkers/worker/index.mjs.map +1 -1
- package/dist/adapters/worker-threads/index.d.mts +25 -0
- package/dist/adapters/worker-threads/index.mjs +43 -0
- package/dist/adapters/worker-threads/index.mjs.map +1 -0
- package/dist/adapters/worker-threads/worker/index.d.mts +26 -0
- package/dist/adapters/worker-threads/worker/index.mjs +46 -0
- package/dist/adapters/worker-threads/worker/index.mjs.map +1 -0
- package/dist/{context-C0RYgQg8.d.mts → context-ZVv99bcM.d.mts} +7 -4
- package/dist/{context-BygZzwvo.mjs → context-ex8urwfs.mjs} +1 -1
- package/dist/context-ex8urwfs.mjs.map +1 -0
- package/dist/{eventa-DWOtUOEf.d.mts → eventa-AJyw28P8.d.mts} +3 -2
- package/dist/index-CI_gUGXg.d.mts +522 -0
- package/dist/index.d.mts +4 -5
- package/dist/index.mjs +3 -3
- package/dist/{internal-C-bIJqJC.mjs → internal-De3z6hO5.mjs} +2 -2
- package/dist/{internal-C-bIJqJC.mjs.map → internal-De3z6hO5.mjs.map} +1 -1
- package/dist/{shared-BExRc0zC.mjs → shared-BZOulnwC.mjs} +2 -2
- package/dist/{shared-BExRc0zC.mjs.map → shared-BZOulnwC.mjs.map} +1 -1
- package/dist/{shared-DRAsaLuo.mjs → shared-BdqIf6iZ.mjs} +4 -10
- package/dist/shared-BdqIf6iZ.mjs.map +1 -0
- package/dist/{shared-Q10kRJA8.d.mts → shared-Cd4CLAdD.d.mts} +2 -2
- package/dist/shared-DgX1R_nY.d.mts +34 -0
- package/dist/src-DZ7si0kE.mjs +1169 -0
- package/dist/src-DZ7si0kE.mjs.map +1 -0
- package/package.json +19 -6
- package/dist/context-BygZzwvo.mjs.map +0 -1
- package/dist/index-DXQGU0bi.d.mts +0 -24
- package/dist/invoke-BB3tmZlr.d.mts +0 -137
- package/dist/shared-DRAsaLuo.mjs.map +0 -1
- package/dist/src-Bnf7MdlN.mjs +0 -284
- package/dist/src-Bnf7MdlN.mjs.map +0 -1
|
@@ -0,0 +1,1169 @@
|
|
|
1
|
+
import { a as defineEventa, l as nanoid } from "./context-ex8urwfs.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/context-extension-invoke-internal.ts
|
|
4
|
+
/**
|
|
5
|
+
* Read the internal invoke configuration from context extensions, if present.
|
|
6
|
+
* This is used by defineInvoke to determine which events should abort inflight invokes.
|
|
7
|
+
*/
|
|
8
|
+
function getContextExtensionInvokeInternalConfig(ctx) {
|
|
9
|
+
const extensions = ctx.extensions;
|
|
10
|
+
if (!extensions || typeof extensions !== "object") return;
|
|
11
|
+
const internal = extensions.__internal;
|
|
12
|
+
if (!internal || typeof internal !== "object") return;
|
|
13
|
+
const invoke = internal.invoke;
|
|
14
|
+
if (!invoke || typeof invoke !== "object") return;
|
|
15
|
+
return invoke;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register a fatal event/match expression that should terminate pending invokes.
|
|
19
|
+
* Adapters call this to wire their error events (e.g. worker error) into invoke.
|
|
20
|
+
*/
|
|
21
|
+
function registerInvokeAbortEventListeners(ctx, eventOrMatch) {
|
|
22
|
+
const extensions = ctx.extensions ?? {};
|
|
23
|
+
const internal = extensions.__internal ?? {};
|
|
24
|
+
const invokeInternal = internal.invoke ?? {};
|
|
25
|
+
const abortOnEvents = Array.isArray(invokeInternal.abortOnEvents) ? invokeInternal.abortOnEvents : [];
|
|
26
|
+
if (!abortOnEvents.includes(eventOrMatch)) abortOnEvents.push(eventOrMatch);
|
|
27
|
+
invokeInternal.abortOnEvents = abortOnEvents;
|
|
28
|
+
internal.invoke = invokeInternal;
|
|
29
|
+
extensions.__internal = internal;
|
|
30
|
+
ctx.extensions = extensions;
|
|
31
|
+
return invokeInternal;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/invoke-shared.ts
|
|
36
|
+
let InvokeEventType = /* @__PURE__ */ function(InvokeEventType$1) {
|
|
37
|
+
InvokeEventType$1[InvokeEventType$1["SendEvent"] = 0] = "SendEvent";
|
|
38
|
+
InvokeEventType$1[InvokeEventType$1["SendEventError"] = 1] = "SendEventError";
|
|
39
|
+
InvokeEventType$1[InvokeEventType$1["SendEventStreamEnd"] = 2] = "SendEventStreamEnd";
|
|
40
|
+
InvokeEventType$1[InvokeEventType$1["SendEventAbort"] = 3] = "SendEventAbort";
|
|
41
|
+
InvokeEventType$1[InvokeEventType$1["ReceiveEvent"] = 4] = "ReceiveEvent";
|
|
42
|
+
InvokeEventType$1[InvokeEventType$1["ReceiveEventError"] = 5] = "ReceiveEventError";
|
|
43
|
+
InvokeEventType$1[InvokeEventType$1["ReceiveEventStreamEnd"] = 6] = "ReceiveEventStreamEnd";
|
|
44
|
+
return InvokeEventType$1;
|
|
45
|
+
}({});
|
|
46
|
+
function defineInvokeEventa(tag) {
|
|
47
|
+
if (!tag) tag = nanoid();
|
|
48
|
+
return {
|
|
49
|
+
sendEvent: {
|
|
50
|
+
...defineEventa(`${tag}-send`),
|
|
51
|
+
invokeType: InvokeEventType.SendEvent
|
|
52
|
+
},
|
|
53
|
+
sendEventError: {
|
|
54
|
+
...defineEventa(`${tag}-send-error`),
|
|
55
|
+
invokeType: InvokeEventType.SendEventError
|
|
56
|
+
},
|
|
57
|
+
sendEventStreamEnd: {
|
|
58
|
+
...defineEventa(`${tag}-send-stream-end`),
|
|
59
|
+
invokeType: InvokeEventType.SendEventStreamEnd
|
|
60
|
+
},
|
|
61
|
+
sendEventAbort: {
|
|
62
|
+
...defineEventa(`${tag}-send-abort`),
|
|
63
|
+
invokeType: InvokeEventType.SendEventAbort
|
|
64
|
+
},
|
|
65
|
+
receiveEvent: {
|
|
66
|
+
...defineEventa(`${tag}-receive`),
|
|
67
|
+
invokeType: InvokeEventType.ReceiveEvent
|
|
68
|
+
},
|
|
69
|
+
receiveEventError: {
|
|
70
|
+
...defineEventa(`${tag}-receive-error`),
|
|
71
|
+
invokeType: InvokeEventType.ReceiveEventError
|
|
72
|
+
},
|
|
73
|
+
receiveEventStreamEnd: {
|
|
74
|
+
...defineEventa(`${tag}-receive-stream-end`),
|
|
75
|
+
invokeType: InvokeEventType.ReceiveEventStreamEnd
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isInvokeEventa(event) {
|
|
80
|
+
if (typeof event !== "object") return false;
|
|
81
|
+
if ("invokeType" in event) return true;
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function isSendEvent(event) {
|
|
85
|
+
if (!isInvokeEventa(event)) return false;
|
|
86
|
+
return event.invokeType === InvokeEventType.SendEvent || event.invokeType === InvokeEventType.SendEventError || event.invokeType === InvokeEventType.SendEventStreamEnd || event.invokeType === InvokeEventType.SendEventAbort;
|
|
87
|
+
}
|
|
88
|
+
function isReceiveEvent(event) {
|
|
89
|
+
if (!isInvokeEventa(event)) return false;
|
|
90
|
+
return event.invokeType === InvokeEventType.ReceiveEvent || event.invokeType === InvokeEventType.ReceiveEventError || event.invokeType === InvokeEventType.ReceiveEventStreamEnd;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/utils.ts
|
|
95
|
+
function randomBetween(min, max) {
|
|
96
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Checks if a value is an AsyncIterable.
|
|
100
|
+
*
|
|
101
|
+
* @param value
|
|
102
|
+
* @returns True if the value is an AsyncIterable.
|
|
103
|
+
*/
|
|
104
|
+
function isAsyncIterable(value) {
|
|
105
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Checks if an object is a ReadableStream.
|
|
109
|
+
*
|
|
110
|
+
* @link https://github.com/cloudflare/workerd/blob/88e8696ce7a5f8969a7e02a2dcfb6504c17c9e8d/src/cloudflare/internal/streaming-forms.ts#L3
|
|
111
|
+
* @param obj
|
|
112
|
+
* @returns True if the object looks like a ReadableStream.
|
|
113
|
+
*/
|
|
114
|
+
function isReadableStream(obj) {
|
|
115
|
+
return !!(obj && typeof obj === "object" && "getReader" in obj && typeof obj.getReader === "function");
|
|
116
|
+
}
|
|
117
|
+
function createAbortError(reason) {
|
|
118
|
+
if (reason instanceof Error && reason.name === "AbortError") return reason;
|
|
119
|
+
if (typeof DOMException !== "undefined") try {
|
|
120
|
+
return new DOMException(reason ? String(reason) : "Aborted", "AbortError");
|
|
121
|
+
} catch {}
|
|
122
|
+
const error = reason instanceof Error ? reason : new Error(reason ? String(reason) : "Aborted");
|
|
123
|
+
error.name = "AbortError";
|
|
124
|
+
return error;
|
|
125
|
+
}
|
|
126
|
+
function isAbortError(error) {
|
|
127
|
+
return error instanceof Error && error.name === "AbortError";
|
|
128
|
+
}
|
|
129
|
+
function createUntilTriggeredOnce(fn) {
|
|
130
|
+
let resolve;
|
|
131
|
+
const promise = new Promise((res) => {
|
|
132
|
+
resolve = res;
|
|
133
|
+
});
|
|
134
|
+
const handler = async (...args) => {
|
|
135
|
+
const res = await fn(...args);
|
|
136
|
+
resolve(res);
|
|
137
|
+
return res;
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
onceTriggered: promise,
|
|
141
|
+
wrapper: handler
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function createUntilTriggered(fn) {
|
|
145
|
+
let resolve;
|
|
146
|
+
const promise = new Promise((res) => {
|
|
147
|
+
resolve = res;
|
|
148
|
+
});
|
|
149
|
+
const handler = (...args) => {
|
|
150
|
+
resolve();
|
|
151
|
+
return fn(...args);
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
promise,
|
|
155
|
+
handler
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function createUntil(options) {
|
|
159
|
+
let resolve;
|
|
160
|
+
const promise = new Promise((res) => {
|
|
161
|
+
resolve = res;
|
|
162
|
+
});
|
|
163
|
+
if (options?.intervalHandler) setInterval(() => {
|
|
164
|
+
options?.intervalHandler?.().then((shouldResolve) => {
|
|
165
|
+
if (shouldResolve) resolve(void 0);
|
|
166
|
+
});
|
|
167
|
+
}, options.interval ?? 50);
|
|
168
|
+
return {
|
|
169
|
+
promise,
|
|
170
|
+
handler: resolve
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/invoke.ts
|
|
176
|
+
function isExtendableInvokeResponseLike(value) {
|
|
177
|
+
if (!isReceiveEvent(value)) return false;
|
|
178
|
+
return typeof value.body?.content === "object" && value.body?.content != null && "response" in value.body.content && (!("invokeResponse" in value.body.content) || "invokeResponse" in value.body.content && (typeof value.body.content.invokeResponse === "object" || typeof value.body.content.invokeResponse === "undefined"));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Create a unary invoke function (client side).
|
|
182
|
+
*
|
|
183
|
+
* It supports unary or streaming requests, but returns a single response.
|
|
184
|
+
* Use `defineStreamInvoke` when you expect a stream of responses.
|
|
185
|
+
*
|
|
186
|
+
* If you want stream input, set `Req` to `ReadableStream<T>` or `AsyncIterable<T>`
|
|
187
|
+
* (or a union type like `T | ReadableStream<T>` for optional streaming).
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* // 1) Define eventa once (shared by client/server)
|
|
192
|
+
* const events = defineInvokeEventa<{ id: string }, { name: string }>()
|
|
193
|
+
*
|
|
194
|
+
* // 2) Client: define invoke function
|
|
195
|
+
* const invoke = defineInvoke(clientCtx, events)
|
|
196
|
+
*
|
|
197
|
+
* // 3) Call
|
|
198
|
+
* const res = await invoke({ name: 'alice' })
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* // Stream request -> unary response
|
|
204
|
+
* const events = defineInvokeEventa<number, ReadableStream<number>>()
|
|
205
|
+
*
|
|
206
|
+
* defineInvokeHandler(serverCtx, events, async (payload) => {
|
|
207
|
+
* let sum = 0
|
|
208
|
+
* for await (const value of payload) {
|
|
209
|
+
* sum += value
|
|
210
|
+
* }
|
|
211
|
+
*
|
|
212
|
+
* return sum
|
|
213
|
+
* })
|
|
214
|
+
*
|
|
215
|
+
* const invoke = defineInvoke(clientCtx, events)
|
|
216
|
+
* const input = new ReadableStream<number>({
|
|
217
|
+
* start(controller) {
|
|
218
|
+
* controller.enqueue(1)
|
|
219
|
+
* controller.enqueue(2)
|
|
220
|
+
* controller.close()
|
|
221
|
+
* },
|
|
222
|
+
* })
|
|
223
|
+
*
|
|
224
|
+
* const total = await invoke(input)
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
227
|
+
* @param ctx Event context on the caller/client side.
|
|
228
|
+
* @param event Invoke event definition created by `defineInvokeEventa`.
|
|
229
|
+
*/
|
|
230
|
+
function defineInvoke(ctx, event) {
|
|
231
|
+
async function getContext() {
|
|
232
|
+
if (typeof ctx === "function") {
|
|
233
|
+
const resolvedCtx = ctx();
|
|
234
|
+
if (resolvedCtx instanceof Promise) return await resolvedCtx;
|
|
235
|
+
return resolvedCtx;
|
|
236
|
+
}
|
|
237
|
+
return ctx;
|
|
238
|
+
}
|
|
239
|
+
function _invoke(req, options) {
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
getContext().then((ctx$1) => {
|
|
242
|
+
const invokeId = nanoid();
|
|
243
|
+
const invokeReceiveEvent = defineEventa(`${event.receiveEvent.id}-${invokeId}`);
|
|
244
|
+
const invokeReceiveEventError = defineEventa(`${event.receiveEventError.id}-${invokeId}`);
|
|
245
|
+
const { signal, ...emitOptions } = options ?? {};
|
|
246
|
+
let finished = false;
|
|
247
|
+
const onAbort = () => {
|
|
248
|
+
ctx$1.emit(event.sendEventAbort, {
|
|
249
|
+
invokeId,
|
|
250
|
+
content: signal?.reason
|
|
251
|
+
}, emitOptions);
|
|
252
|
+
finishReject(createAbortError(signal?.reason));
|
|
253
|
+
};
|
|
254
|
+
const invokeInternalConfig = getContextExtensionInvokeInternalConfig(ctx$1);
|
|
255
|
+
const abortOffs = [];
|
|
256
|
+
const onAbortEvent = (payload, eventOptions) => {
|
|
257
|
+
const mappedError = invokeInternalConfig?.mapAbortError?.(payload, eventOptions);
|
|
258
|
+
if (typeof mappedError !== "undefined") {
|
|
259
|
+
finishReject(mappedError);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const body = payload?.body;
|
|
263
|
+
finishReject(body && typeof body === "object" && "error" in body ? body.error : typeof body !== "undefined" ? body : payload);
|
|
264
|
+
};
|
|
265
|
+
const cleanup = () => {
|
|
266
|
+
ctx$1.off(invokeReceiveEvent);
|
|
267
|
+
ctx$1.off(invokeReceiveEventError);
|
|
268
|
+
for (const off of abortOffs) off();
|
|
269
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
270
|
+
};
|
|
271
|
+
const finishReject = (error) => {
|
|
272
|
+
if (finished) return;
|
|
273
|
+
finished = true;
|
|
274
|
+
reject(error);
|
|
275
|
+
cleanup();
|
|
276
|
+
};
|
|
277
|
+
const finishResolve = (value) => {
|
|
278
|
+
if (finished) return;
|
|
279
|
+
finished = true;
|
|
280
|
+
resolve(value);
|
|
281
|
+
cleanup();
|
|
282
|
+
};
|
|
283
|
+
ctx$1.on(invokeReceiveEvent, (payload) => {
|
|
284
|
+
if (!payload.body) return;
|
|
285
|
+
if (payload.body.invokeId !== invokeId) return;
|
|
286
|
+
const { content } = payload.body;
|
|
287
|
+
finishResolve(content);
|
|
288
|
+
});
|
|
289
|
+
ctx$1.on(invokeReceiveEventError, (payload) => {
|
|
290
|
+
if (!payload.body) return;
|
|
291
|
+
if (payload.body.invokeId !== invokeId) return;
|
|
292
|
+
const { error } = payload.body.content;
|
|
293
|
+
finishReject(error);
|
|
294
|
+
});
|
|
295
|
+
if (invokeInternalConfig?.abortOnEvents?.length) for (const eventOrMatch of invokeInternalConfig.abortOnEvents) abortOffs.push(ctx$1.on(eventOrMatch, onAbortEvent));
|
|
296
|
+
if (signal) {
|
|
297
|
+
if (signal.aborted) {
|
|
298
|
+
onAbort();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
302
|
+
}
|
|
303
|
+
if (!isReadableStream(req) && !isAsyncIterable(req)) ctx$1.emit(event.sendEvent, {
|
|
304
|
+
invokeId,
|
|
305
|
+
content: req
|
|
306
|
+
}, emitOptions);
|
|
307
|
+
else {
|
|
308
|
+
const sendChunk = (chunk) => {
|
|
309
|
+
if (finished) return;
|
|
310
|
+
ctx$1.emit(event.sendEvent, {
|
|
311
|
+
invokeId,
|
|
312
|
+
content: chunk,
|
|
313
|
+
isReqStream: true
|
|
314
|
+
}, emitOptions);
|
|
315
|
+
};
|
|
316
|
+
const sendEnd = () => {
|
|
317
|
+
if (finished) return;
|
|
318
|
+
ctx$1.emit(event.sendEventStreamEnd, {
|
|
319
|
+
invokeId,
|
|
320
|
+
content: void 0
|
|
321
|
+
}, emitOptions);
|
|
322
|
+
};
|
|
323
|
+
const pump = async () => {
|
|
324
|
+
try {
|
|
325
|
+
for await (const chunk of req) {
|
|
326
|
+
if (signal?.aborted) return;
|
|
327
|
+
sendChunk(chunk);
|
|
328
|
+
}
|
|
329
|
+
sendEnd();
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (signal?.aborted) return;
|
|
332
|
+
if (isAbortError(error)) {
|
|
333
|
+
ctx$1.emit(event.sendEventAbort, {
|
|
334
|
+
invokeId,
|
|
335
|
+
content: error
|
|
336
|
+
}, emitOptions);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
ctx$1.emit(event.sendEventError, {
|
|
340
|
+
invokeId,
|
|
341
|
+
content: error
|
|
342
|
+
}, emitOptions);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
pump();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return _invoke;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Create a map of invoke functions from a map of invoke events (client side).
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```ts
|
|
357
|
+
* const events = {
|
|
358
|
+
* double: defineInvokeEventa<number, number>(),
|
|
359
|
+
* greet: defineInvokeEventa<string, { name: string }>(),
|
|
360
|
+
* }
|
|
361
|
+
*
|
|
362
|
+
* const invokes = defineInvokes(ctx, events)
|
|
363
|
+
* const result = await invokes.double(2)
|
|
364
|
+
* ```
|
|
365
|
+
*
|
|
366
|
+
* @param ctx Event context on the caller/client side.
|
|
367
|
+
* @param events Map of invoke events created by `defineInvokeEventa`.
|
|
368
|
+
*/
|
|
369
|
+
function defineInvokes(ctx, events) {
|
|
370
|
+
return Object.keys(events).reduce((invokes, key) => {
|
|
371
|
+
invokes[key] = defineInvoke(ctx, events[key]);
|
|
372
|
+
return invokes;
|
|
373
|
+
}, {});
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Define a unary invoke handler (server side).
|
|
377
|
+
*
|
|
378
|
+
* The handler can accept a unary or streaming request; it must return
|
|
379
|
+
* a single response (or an extendable response envelope).
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```ts
|
|
383
|
+
* const events = defineInvokeEventa<{ id: string }, { name: string }>()
|
|
384
|
+
*
|
|
385
|
+
* defineInvokeHandler(serverCtx, events, ({ name }) => ({
|
|
386
|
+
* id: `user-${name}`,
|
|
387
|
+
* }))
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* @param ctx Event context on the handler/server side.
|
|
391
|
+
* @param event Invoke event definition created by `defineInvokeEventa`.
|
|
392
|
+
* @param handler Handler that returns a response (or response + metadata).
|
|
393
|
+
*/
|
|
394
|
+
function defineInvokeHandler(ctx, event, handler) {
|
|
395
|
+
if (!ctx.invokeHandlers) ctx.invokeHandlers = /* @__PURE__ */ new Map();
|
|
396
|
+
let handlers = ctx.invokeHandlers?.get(event.sendEvent.id);
|
|
397
|
+
if (!handlers) {
|
|
398
|
+
handlers = /* @__PURE__ */ new Map();
|
|
399
|
+
ctx.invokeHandlers?.set(event.sendEvent.id, handlers);
|
|
400
|
+
}
|
|
401
|
+
let internalHandler = handlers.get(handler);
|
|
402
|
+
if (!internalHandler) {
|
|
403
|
+
const streamStates = /* @__PURE__ */ new Map();
|
|
404
|
+
const abortControllers = /* @__PURE__ */ new Map();
|
|
405
|
+
const abortReasons = /* @__PURE__ */ new Map();
|
|
406
|
+
const scheduleAbort = (controller, reason) => {
|
|
407
|
+
if (typeof queueMicrotask !== "undefined") {
|
|
408
|
+
queueMicrotask(() => controller.abort(reason));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
Promise.resolve().then(() => controller.abort(reason));
|
|
412
|
+
};
|
|
413
|
+
const handleInvoke = async (invokeId, payload, options) => {
|
|
414
|
+
const abortController = new AbortController();
|
|
415
|
+
abortControllers.set(invokeId, abortController);
|
|
416
|
+
if (abortReasons.has(invokeId)) scheduleAbort(abortController, abortReasons.get(invokeId));
|
|
417
|
+
const handlerOptions = options ? {
|
|
418
|
+
...options,
|
|
419
|
+
abortController
|
|
420
|
+
} : { abortController };
|
|
421
|
+
try {
|
|
422
|
+
const response = await handler(payload, handlerOptions);
|
|
423
|
+
ctx.emit({
|
|
424
|
+
...defineEventa(`${event.receiveEvent.id}-${invokeId}`),
|
|
425
|
+
invokeType: event.receiveEvent.invokeType
|
|
426
|
+
}, {
|
|
427
|
+
invokeId,
|
|
428
|
+
content: response
|
|
429
|
+
}, options);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
ctx.emit({
|
|
432
|
+
...defineEventa(`${event.receiveEventError.id}-${invokeId}`),
|
|
433
|
+
invokeType: event.receiveEventError.invokeType
|
|
434
|
+
}, {
|
|
435
|
+
invokeId,
|
|
436
|
+
content: { error }
|
|
437
|
+
}, options);
|
|
438
|
+
} finally {
|
|
439
|
+
abortControllers.delete(invokeId);
|
|
440
|
+
abortReasons.delete(invokeId);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const onSend = async (payload, options) => {
|
|
444
|
+
if (!payload.body) return;
|
|
445
|
+
if (!payload.body.invokeId) return;
|
|
446
|
+
const invokeId = payload.body.invokeId;
|
|
447
|
+
if (payload.body.isReqStream) {
|
|
448
|
+
let controller = streamStates.get(invokeId);
|
|
449
|
+
if (!controller) {
|
|
450
|
+
let localController;
|
|
451
|
+
const reqStream = new ReadableStream({ start(c) {
|
|
452
|
+
localController = c;
|
|
453
|
+
} });
|
|
454
|
+
controller = localController;
|
|
455
|
+
streamStates.set(invokeId, controller);
|
|
456
|
+
handleInvoke(invokeId, reqStream, options);
|
|
457
|
+
}
|
|
458
|
+
controller.enqueue(payload.body.content);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
handleInvoke(invokeId, payload.body?.content, options);
|
|
462
|
+
};
|
|
463
|
+
const onSendStreamEnd = (payload, options) => {
|
|
464
|
+
if (!payload.body) return;
|
|
465
|
+
if (!payload.body.invokeId) return;
|
|
466
|
+
const invokeId = payload.body.invokeId;
|
|
467
|
+
let controller = streamStates.get(invokeId);
|
|
468
|
+
if (!controller) {
|
|
469
|
+
let localController;
|
|
470
|
+
const reqStream = new ReadableStream({ start(c) {
|
|
471
|
+
localController = c;
|
|
472
|
+
} });
|
|
473
|
+
controller = localController;
|
|
474
|
+
streamStates.set(invokeId, controller);
|
|
475
|
+
handleInvoke(invokeId, reqStream, options);
|
|
476
|
+
}
|
|
477
|
+
controller.close();
|
|
478
|
+
streamStates.delete(invokeId);
|
|
479
|
+
};
|
|
480
|
+
const onSendAbort = (payload, options) => {
|
|
481
|
+
if (!payload.body) return;
|
|
482
|
+
if (!payload.body.invokeId) return;
|
|
483
|
+
const invokeId = payload.body.invokeId;
|
|
484
|
+
const reason = payload.body.content;
|
|
485
|
+
const abortController = abortControllers.get(invokeId);
|
|
486
|
+
if (!abortController) {
|
|
487
|
+
abortReasons.set(invokeId, reason);
|
|
488
|
+
let streamController$1 = streamStates.get(invokeId);
|
|
489
|
+
if (!streamController$1) {
|
|
490
|
+
let localController;
|
|
491
|
+
const reqStream = new ReadableStream({ start(c) {
|
|
492
|
+
localController = c;
|
|
493
|
+
} });
|
|
494
|
+
streamController$1 = localController;
|
|
495
|
+
streamStates.set(invokeId, streamController$1);
|
|
496
|
+
handleInvoke(invokeId, reqStream, options);
|
|
497
|
+
}
|
|
498
|
+
streamController$1.error(createAbortError(reason));
|
|
499
|
+
streamStates.delete(invokeId);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
scheduleAbort(abortController, reason);
|
|
503
|
+
const streamController = streamStates.get(invokeId);
|
|
504
|
+
if (streamController) {
|
|
505
|
+
streamController.error(createAbortError(reason));
|
|
506
|
+
streamStates.delete(invokeId);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
internalHandler = {
|
|
510
|
+
onSend,
|
|
511
|
+
onSendStreamEnd,
|
|
512
|
+
onSendAbort
|
|
513
|
+
};
|
|
514
|
+
handlers.set(handler, internalHandler);
|
|
515
|
+
ctx.on(event.sendEvent, internalHandler.onSend);
|
|
516
|
+
ctx.on(event.sendEventStreamEnd, internalHandler.onSendStreamEnd);
|
|
517
|
+
ctx.on(event.sendEventAbort, internalHandler.onSendAbort);
|
|
518
|
+
}
|
|
519
|
+
return () => {
|
|
520
|
+
ctx.off(event.sendEvent, internalHandler.onSend);
|
|
521
|
+
ctx.off(event.sendEventStreamEnd, internalHandler.onSendStreamEnd);
|
|
522
|
+
ctx.off(event.sendEventAbort, internalHandler.onSendAbort);
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Define multiple invoke handlers in batch (server side).
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* const events = {
|
|
531
|
+
* double: defineInvokeEventa<number, number>(),
|
|
532
|
+
* greet: defineInvokeEventa<string, { name: string }>(),
|
|
533
|
+
* }
|
|
534
|
+
*
|
|
535
|
+
* defineInvokeHandlers(ctx, events, {
|
|
536
|
+
* double: value => value * 2,
|
|
537
|
+
* greet: ({ name }) => `hi ${name}`,
|
|
538
|
+
* })
|
|
539
|
+
* ```
|
|
540
|
+
*
|
|
541
|
+
* @param ctx Event context on the handler/server side.
|
|
542
|
+
* @param events Map of invoke events created by `defineInvokeEventa`.
|
|
543
|
+
* @param handlers Map of handlers keyed by event name.
|
|
544
|
+
*/
|
|
545
|
+
function defineInvokeHandlers(ctx, events, handlers) {
|
|
546
|
+
const eventKeys = Object.keys(events);
|
|
547
|
+
const handlerKeys = new Set(Object.keys(handlers));
|
|
548
|
+
if (eventKeys.length !== handlerKeys.size || !eventKeys.every((key) => handlerKeys.has(key))) throw new Error("The keys of events and handlers must match.");
|
|
549
|
+
return eventKeys.reduce((returnValues, key) => {
|
|
550
|
+
returnValues[key] = defineInvokeHandler(ctx, events[key], handlers[key]);
|
|
551
|
+
return returnValues;
|
|
552
|
+
}, {});
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Remove one or all invoke handlers for a specific invoke event (server side).
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* const off = defineInvokeHandler(ctx, events, handler)
|
|
560
|
+
* off() // remove one handler
|
|
561
|
+
*
|
|
562
|
+
* // or remove all handlers for the event:
|
|
563
|
+
* undefineInvokeHandler(ctx, events)
|
|
564
|
+
* ```
|
|
565
|
+
*
|
|
566
|
+
* @param ctx Event context on the handler/server side.
|
|
567
|
+
* @param event Invoke event definition created by `defineInvokeEventa`.
|
|
568
|
+
* @param handler Specific handler to remove (omit to remove all).
|
|
569
|
+
* @returns `true` if at least one handler was removed, `false` otherwise
|
|
570
|
+
*/
|
|
571
|
+
function undefineInvokeHandler(ctx, event, handler) {
|
|
572
|
+
if (!ctx.invokeHandlers) return false;
|
|
573
|
+
const handlers = ctx.invokeHandlers?.get(event.sendEvent.id);
|
|
574
|
+
if (!handlers) return false;
|
|
575
|
+
if (handler) {
|
|
576
|
+
const internalHandler = handlers.get(handler);
|
|
577
|
+
if (!internalHandler) return false;
|
|
578
|
+
ctx.off(event.sendEvent, internalHandler.onSend);
|
|
579
|
+
ctx.off(event.sendEventStreamEnd, internalHandler.onSendStreamEnd);
|
|
580
|
+
ctx.off(event.sendEventAbort, internalHandler.onSendAbort);
|
|
581
|
+
ctx.invokeHandlers.delete(event.sendEvent.id);
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
let returnValue = false;
|
|
585
|
+
for (const internalHandlers of handlers.values()) {
|
|
586
|
+
ctx.off(event.sendEvent, internalHandlers.onSend);
|
|
587
|
+
ctx.off(event.sendEventStreamEnd, internalHandlers.onSendStreamEnd);
|
|
588
|
+
ctx.off(event.sendEventAbort, internalHandlers.onSendAbort);
|
|
589
|
+
returnValue = true;
|
|
590
|
+
}
|
|
591
|
+
ctx.invokeHandlers.delete(event.sendEvent.id);
|
|
592
|
+
return returnValue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/invoke-extension-transfer.ts
|
|
597
|
+
function withTransfer(body, transfer) {
|
|
598
|
+
return {
|
|
599
|
+
response: body,
|
|
600
|
+
invokeResponse: { transfer: transfer ?? [] }
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/invoke-remote-methods.ts
|
|
606
|
+
const DEFAULT_FUNCTION_STUB_OPTIONS = {
|
|
607
|
+
maxDepth: 32,
|
|
608
|
+
maxFunctions: 32,
|
|
609
|
+
tagPrefix: "eventa-invoke-fn-",
|
|
610
|
+
onDisallowedTag: "ignore",
|
|
611
|
+
autoDisposeMs: 0,
|
|
612
|
+
strict: false
|
|
613
|
+
};
|
|
614
|
+
function createRemoteMethodTagPrefix(prefix = DEFAULT_FUNCTION_STUB_OPTIONS.tagPrefix) {
|
|
615
|
+
return `${prefix}${nanoid()}-`;
|
|
616
|
+
}
|
|
617
|
+
function normalizeFunctionStubOptions(options) {
|
|
618
|
+
if (options === true) return {
|
|
619
|
+
allow: true,
|
|
620
|
+
...DEFAULT_FUNCTION_STUB_OPTIONS
|
|
621
|
+
};
|
|
622
|
+
if (!options) return {
|
|
623
|
+
allow: false,
|
|
624
|
+
...DEFAULT_FUNCTION_STUB_OPTIONS
|
|
625
|
+
};
|
|
626
|
+
return {
|
|
627
|
+
allow: options.allow ?? true,
|
|
628
|
+
maxDepth: options.maxDepth ?? DEFAULT_FUNCTION_STUB_OPTIONS.maxDepth,
|
|
629
|
+
maxFunctions: options.maxFunctions ?? DEFAULT_FUNCTION_STUB_OPTIONS.maxFunctions,
|
|
630
|
+
tagPrefix: options.tagPrefix ?? DEFAULT_FUNCTION_STUB_OPTIONS.tagPrefix,
|
|
631
|
+
onDisallowedTag: options.onDisallowedTag ?? DEFAULT_FUNCTION_STUB_OPTIONS.onDisallowedTag,
|
|
632
|
+
autoDisposeMs: options.autoDisposeMs ?? DEFAULT_FUNCTION_STUB_OPTIONS.autoDisposeMs,
|
|
633
|
+
strict: options.strict ?? DEFAULT_FUNCTION_STUB_OPTIONS.strict
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function resolveFunctionStubOptions(defaults, override) {
|
|
637
|
+
const base = normalizeFunctionStubOptions(defaults);
|
|
638
|
+
if (typeof override === "undefined") return base;
|
|
639
|
+
if (typeof override === "boolean") return {
|
|
640
|
+
...base,
|
|
641
|
+
allow: override
|
|
642
|
+
};
|
|
643
|
+
return {
|
|
644
|
+
...base,
|
|
645
|
+
...normalizeFunctionStubOptions(override)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function isPlainObject(value) {
|
|
649
|
+
if (value == null || typeof value !== "object") return false;
|
|
650
|
+
const prototype = Object.getPrototypeOf(value);
|
|
651
|
+
return prototype === Object.prototype || prototype === null;
|
|
652
|
+
}
|
|
653
|
+
function isInvokeFunctionStubPayload(value) {
|
|
654
|
+
if (!isPlainObject(value)) return false;
|
|
655
|
+
if (!("__eventaInvoke" in value)) return false;
|
|
656
|
+
const stub = value.__eventaInvoke;
|
|
657
|
+
return !!stub && typeof stub === "object" && typeof stub.tag === "string";
|
|
658
|
+
}
|
|
659
|
+
function hasInvokeFunctionStubKey(value) {
|
|
660
|
+
return isPlainObject(value) && "__eventaInvoke" in value;
|
|
661
|
+
}
|
|
662
|
+
function serializeInvokeFunctionPayload(value, ctx, options) {
|
|
663
|
+
if (!options.allow) return {
|
|
664
|
+
value,
|
|
665
|
+
dispose: () => void 0
|
|
666
|
+
};
|
|
667
|
+
let functionCount = 0;
|
|
668
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
669
|
+
const disposers = [];
|
|
670
|
+
const walk = (input, depth) => {
|
|
671
|
+
if (typeof input === "function") {
|
|
672
|
+
if (functionCount >= options.maxFunctions) throw new Error(`Too many function stubs in invoke payload (max ${options.maxFunctions}).`);
|
|
673
|
+
functionCount += 1;
|
|
674
|
+
const tag = `${options.tagPrefix}${nanoid()}`;
|
|
675
|
+
const off = defineInvokeHandler(ctx, defineInvokeEventa(tag), (payload, handlerOptions) => {
|
|
676
|
+
return input(payload, handlerOptions);
|
|
677
|
+
});
|
|
678
|
+
disposers.push(off);
|
|
679
|
+
return { __eventaInvoke: { tag } };
|
|
680
|
+
}
|
|
681
|
+
if (input == null || typeof input !== "object") return input;
|
|
682
|
+
if (depth > options.maxDepth) throw new Error(`Invoke payload is too deep (max ${options.maxDepth}).`);
|
|
683
|
+
if (seen.has(input)) return seen.get(input);
|
|
684
|
+
if (Array.isArray(input)) {
|
|
685
|
+
const output$1 = Array.from({ length: input.length });
|
|
686
|
+
seen.set(input, output$1);
|
|
687
|
+
for (let i = 0; i < input.length; i += 1) output$1[i] = walk(input[i], depth + 1);
|
|
688
|
+
return output$1;
|
|
689
|
+
}
|
|
690
|
+
if (!isPlainObject(input)) return input;
|
|
691
|
+
const output = Object.create(null);
|
|
692
|
+
seen.set(input, output);
|
|
693
|
+
for (const [key, child] of Object.entries(input)) output[key] = walk(child, depth + 1);
|
|
694
|
+
return output;
|
|
695
|
+
};
|
|
696
|
+
return {
|
|
697
|
+
value: walk(value, 0),
|
|
698
|
+
dispose: () => {
|
|
699
|
+
for (const off of disposers) off();
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function deserializeInvokeFunctionPayload(value, ctx, options) {
|
|
704
|
+
if (!options.allow) return value;
|
|
705
|
+
let functionCount = 0;
|
|
706
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
707
|
+
const walk = (input, depth) => {
|
|
708
|
+
if (input == null || typeof input !== "object") return input;
|
|
709
|
+
if (depth > options.maxDepth) throw new Error(`Invoke payload is too deep (max ${options.maxDepth}).`);
|
|
710
|
+
if (isInvokeFunctionStubPayload(input)) {
|
|
711
|
+
if (functionCount >= options.maxFunctions) throw new Error(`Too many function stubs in invoke payload (max ${options.maxFunctions}).`);
|
|
712
|
+
const tag = input.__eventaInvoke.tag;
|
|
713
|
+
if (options.tagPrefix && !tag.startsWith(options.tagPrefix)) {
|
|
714
|
+
if (options.onDisallowedTag === "throw") throw new Error(`Invoke function tag not allowed: ${tag}`);
|
|
715
|
+
return input;
|
|
716
|
+
}
|
|
717
|
+
functionCount += 1;
|
|
718
|
+
return defineInvoke(ctx, defineInvokeEventa(tag));
|
|
719
|
+
} else if (options.strict && hasInvokeFunctionStubKey(input)) throw new Error("Invalid invoke function stub payload.");
|
|
720
|
+
if (seen.has(input)) return seen.get(input);
|
|
721
|
+
if (Array.isArray(input)) {
|
|
722
|
+
const output$1 = Array.from({ length: input.length });
|
|
723
|
+
seen.set(input, output$1);
|
|
724
|
+
for (let i = 0; i < input.length; i += 1) output$1[i] = walk(input[i], depth + 1);
|
|
725
|
+
return output$1;
|
|
726
|
+
}
|
|
727
|
+
if (!isPlainObject(input)) return input;
|
|
728
|
+
const output = Object.create(null);
|
|
729
|
+
seen.set(input, output);
|
|
730
|
+
for (const [key, child] of Object.entries(input)) output[key] = walk(child, depth + 1);
|
|
731
|
+
return output;
|
|
732
|
+
};
|
|
733
|
+
return walk(value, 0);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Enable "remote method" payloads for invoke: functions in the request body are
|
|
737
|
+
* serialized into stub descriptors and rehydrated into invoke callers on the
|
|
738
|
+
* receiving side.
|
|
739
|
+
*
|
|
740
|
+
* This is an adapter around the plain-value RPC primitives `defineInvoke` and
|
|
741
|
+
* `defineInvokeHandler`. It keeps the core invoke APIs clean while offering an
|
|
742
|
+
* opt-in bridge for function values.
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* ```ts
|
|
746
|
+
* const remote = withRemoteMethods({ allow: true })
|
|
747
|
+
* const events = defineInvokeEventa<{ output: number }, { helper: (n: number) => Promise<number> }>()
|
|
748
|
+
*
|
|
749
|
+
* // server (handler)
|
|
750
|
+
* remote.defineInvokeHandler(serverCtx, events, async ({ helper }) => {
|
|
751
|
+
* const output = await helper(21)
|
|
752
|
+
* return { output }
|
|
753
|
+
* })
|
|
754
|
+
*
|
|
755
|
+
* // client (caller)
|
|
756
|
+
* const invoke = remote.defineInvoke(clientCtx, events)
|
|
757
|
+
* const result = await invoke({ helper: async n => n * 2 }, { functionStubs: true })
|
|
758
|
+
* ```
|
|
759
|
+
*
|
|
760
|
+
* @example
|
|
761
|
+
* ```ts
|
|
762
|
+
* // Manual cleanup when you fire-and-forget or cancel midway:
|
|
763
|
+
* const invoke = remote.defineInvoke(clientCtx, events)
|
|
764
|
+
* const result = invoke({ helper: () => 'ok' }, { functionStubs: true })
|
|
765
|
+
* result.dispose()
|
|
766
|
+
* await result
|
|
767
|
+
* ```
|
|
768
|
+
*
|
|
769
|
+
* Security notes:
|
|
770
|
+
* - This feature is off by default. Enabling it allows the remote side to call
|
|
771
|
+
* back into your process; only use with trusted peers.
|
|
772
|
+
* - Function stubs are tagged; prefer a unique `tagPrefix` to avoid collisions
|
|
773
|
+
* (see `createRemoteMethodTagPrefix`).
|
|
774
|
+
* - `maxDepth`, `maxFunctions`, and `autoDisposeMs` limit attack surface and
|
|
775
|
+
* resource usage. `autoDisposeMs` is useful for fire-and-forget calls.
|
|
776
|
+
* - Objects are rebuilt with a null prototype to mitigate `__proto__` pollution.
|
|
777
|
+
* - Enable `strict` to reject malformed `__eventaInvoke` payloads.
|
|
778
|
+
*
|
|
779
|
+
* @param defaultOptions Defaults for function stub behavior. Use `{ allow: true }`
|
|
780
|
+
* to enable, or provide `maxDepth`, `maxFunctions`, `tagPrefix`,
|
|
781
|
+
* `onDisallowedTag`, `autoDisposeMs`, and `strict` for stricter control.
|
|
782
|
+
*/
|
|
783
|
+
function withRemoteMethods(defaultOptions) {
|
|
784
|
+
return {
|
|
785
|
+
defineInvoke(ctx, event) {
|
|
786
|
+
const baseInvoke = defineInvoke(ctx, event);
|
|
787
|
+
const invoke = ((req, options) => {
|
|
788
|
+
const { functionStubs, ...invokeOptions } = options ?? {};
|
|
789
|
+
const normalizedOptions = resolveFunctionStubOptions(defaultOptions, functionStubs);
|
|
790
|
+
if (!normalizedOptions.allow) {
|
|
791
|
+
const wrapped$1 = baseInvoke(req, invokeOptions);
|
|
792
|
+
wrapped$1.dispose = () => void 0;
|
|
793
|
+
return wrapped$1;
|
|
794
|
+
}
|
|
795
|
+
let serialized;
|
|
796
|
+
try {
|
|
797
|
+
serialized = serializeInvokeFunctionPayload(req, ctx, normalizedOptions);
|
|
798
|
+
} catch (error) {
|
|
799
|
+
const rejected = Promise.reject(error);
|
|
800
|
+
rejected.dispose = () => void 0;
|
|
801
|
+
return rejected;
|
|
802
|
+
}
|
|
803
|
+
let disposed = false;
|
|
804
|
+
const dispose = () => {
|
|
805
|
+
if (disposed) return;
|
|
806
|
+
disposed = true;
|
|
807
|
+
serialized.dispose();
|
|
808
|
+
};
|
|
809
|
+
let autoDisposeTimer;
|
|
810
|
+
if (normalizedOptions.autoDisposeMs > 0) autoDisposeTimer = setTimeout(() => {
|
|
811
|
+
dispose();
|
|
812
|
+
}, normalizedOptions.autoDisposeMs);
|
|
813
|
+
const finalize = () => {
|
|
814
|
+
if (autoDisposeTimer) clearTimeout(autoDisposeTimer);
|
|
815
|
+
dispose();
|
|
816
|
+
};
|
|
817
|
+
const wrapped = baseInvoke(serialized.value, invokeOptions).finally(finalize);
|
|
818
|
+
wrapped.dispose = finalize;
|
|
819
|
+
return wrapped;
|
|
820
|
+
});
|
|
821
|
+
return invoke;
|
|
822
|
+
},
|
|
823
|
+
defineInvokeHandler(ctx, event, handler) {
|
|
824
|
+
const normalizedOptions = resolveFunctionStubOptions(defaultOptions);
|
|
825
|
+
return defineInvokeHandler(ctx, event, async (payload, options) => {
|
|
826
|
+
return handler(normalizedOptions.allow ? deserializeInvokeFunctionPayload(payload, ctx, normalizedOptions) : payload, options);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
//#endregion
|
|
833
|
+
//#region src/stream.ts
|
|
834
|
+
/**
|
|
835
|
+
* Create a stream invoke function (client side).
|
|
836
|
+
*
|
|
837
|
+
* Use when the response is streamed and the request may be unary or streaming.
|
|
838
|
+
*
|
|
839
|
+
* Common patterns:
|
|
840
|
+
* - Unary request -> stream response (server-streaming)
|
|
841
|
+
* - Stream request -> stream response (bi-directional streaming)
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* ```ts
|
|
845
|
+
* // 1) Define eventa once (shared by client/server)
|
|
846
|
+
* const events = defineInvokeEventa<Progress | Result, Params>()
|
|
847
|
+
*
|
|
848
|
+
* // 2) Client: define invoke function
|
|
849
|
+
* const invoke = defineStreamInvoke(clientCtx, events)
|
|
850
|
+
*
|
|
851
|
+
* // 3) Call with unary request
|
|
852
|
+
* for await (const msg of invoke({ name: 'alice' })) {
|
|
853
|
+
* console.log(msg)
|
|
854
|
+
* }
|
|
855
|
+
* ```
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```ts
|
|
859
|
+
* // Client-streaming request
|
|
860
|
+
* const input = new ReadableStream<number>({
|
|
861
|
+
* start(c) { c.enqueue(1); c.enqueue(2); c.close() },
|
|
862
|
+
* })
|
|
863
|
+
*
|
|
864
|
+
* for await (const msg of invoke(input)) {
|
|
865
|
+
* console.log(msg)
|
|
866
|
+
* }
|
|
867
|
+
* ```
|
|
868
|
+
*
|
|
869
|
+
* @param clientCtx Event context on the caller/client side.
|
|
870
|
+
* @param event Invoke event definition created by `defineInvokeEventa`.
|
|
871
|
+
*/
|
|
872
|
+
function defineStreamInvoke(clientCtx, event) {
|
|
873
|
+
return (req, options) => {
|
|
874
|
+
const invokeId = nanoid();
|
|
875
|
+
const { signal, ...emitOptions } = options ?? {};
|
|
876
|
+
let onAbort;
|
|
877
|
+
const invokeReceiveEvent = defineEventa(`${event.receiveEvent.id}-${invokeId}`);
|
|
878
|
+
const invokeReceiveEventError = defineEventa(`${event.receiveEventError.id}-${invokeId}`);
|
|
879
|
+
const invokeReceiveEventStreamEnd = defineEventa(`${event.receiveEventStreamEnd.id}-${invokeId}`);
|
|
880
|
+
const stream = new ReadableStream({
|
|
881
|
+
start(controller) {
|
|
882
|
+
const cleanup = () => {
|
|
883
|
+
clientCtx.off(invokeReceiveEvent);
|
|
884
|
+
clientCtx.off(invokeReceiveEventError);
|
|
885
|
+
clientCtx.off(invokeReceiveEventStreamEnd);
|
|
886
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
887
|
+
};
|
|
888
|
+
onAbort = () => {
|
|
889
|
+
clientCtx.emit(event.sendEventAbort, {
|
|
890
|
+
invokeId,
|
|
891
|
+
content: signal?.reason
|
|
892
|
+
}, emitOptions);
|
|
893
|
+
controller.error(createAbortError(signal?.reason));
|
|
894
|
+
cleanup();
|
|
895
|
+
};
|
|
896
|
+
clientCtx.on(invokeReceiveEvent, (payload) => {
|
|
897
|
+
if (!payload.body) return;
|
|
898
|
+
if (payload.body.invokeId !== invokeId) return;
|
|
899
|
+
controller.enqueue(payload.body.content);
|
|
900
|
+
});
|
|
901
|
+
clientCtx.on(invokeReceiveEventError, (payload) => {
|
|
902
|
+
if (!payload.body) return;
|
|
903
|
+
if (payload.body.invokeId !== invokeId) return;
|
|
904
|
+
controller.error(payload.body.content.error);
|
|
905
|
+
cleanup();
|
|
906
|
+
});
|
|
907
|
+
clientCtx.on(invokeReceiveEventStreamEnd, (payload) => {
|
|
908
|
+
if (!payload.body) return;
|
|
909
|
+
if (payload.body.invokeId !== invokeId) return;
|
|
910
|
+
controller.close();
|
|
911
|
+
cleanup();
|
|
912
|
+
});
|
|
913
|
+
if (signal && onAbort) {
|
|
914
|
+
if (signal.aborted) {
|
|
915
|
+
onAbort();
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
cancel(reason) {
|
|
922
|
+
clientCtx.emit(event.sendEventAbort, {
|
|
923
|
+
invokeId,
|
|
924
|
+
content: reason
|
|
925
|
+
}, emitOptions);
|
|
926
|
+
clientCtx.off(invokeReceiveEvent);
|
|
927
|
+
clientCtx.off(invokeReceiveEventError);
|
|
928
|
+
clientCtx.off(invokeReceiveEventStreamEnd);
|
|
929
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
if (isReadableStream(req) || isAsyncIterable(req)) {
|
|
933
|
+
const sendChunk = (chunk) => {
|
|
934
|
+
clientCtx.emit(event.sendEvent, {
|
|
935
|
+
invokeId,
|
|
936
|
+
content: chunk,
|
|
937
|
+
isReqStream: true
|
|
938
|
+
}, emitOptions);
|
|
939
|
+
};
|
|
940
|
+
const sendEnd = () => {
|
|
941
|
+
clientCtx.emit(event.sendEventStreamEnd, {
|
|
942
|
+
invokeId,
|
|
943
|
+
content: void 0
|
|
944
|
+
}, emitOptions);
|
|
945
|
+
};
|
|
946
|
+
const pump = async () => {
|
|
947
|
+
try {
|
|
948
|
+
for await (const chunk of req) {
|
|
949
|
+
if (signal?.aborted) return;
|
|
950
|
+
sendChunk(chunk);
|
|
951
|
+
}
|
|
952
|
+
sendEnd();
|
|
953
|
+
} catch (error) {
|
|
954
|
+
if (signal?.aborted) return;
|
|
955
|
+
if (isAbortError(error)) {
|
|
956
|
+
clientCtx.emit(event.sendEventAbort, {
|
|
957
|
+
invokeId,
|
|
958
|
+
content: error
|
|
959
|
+
}, emitOptions);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
clientCtx.emit(event.sendEventError, {
|
|
963
|
+
invokeId,
|
|
964
|
+
content: error
|
|
965
|
+
}, emitOptions);
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
pump();
|
|
969
|
+
} else clientCtx.emit(event.sendEvent, {
|
|
970
|
+
invokeId,
|
|
971
|
+
content: req
|
|
972
|
+
}, emitOptions);
|
|
973
|
+
return stream;
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Define a stream invoke handler (server side).
|
|
978
|
+
*
|
|
979
|
+
* The handler can receive either:
|
|
980
|
+
* - a unary request `Req`
|
|
981
|
+
* - a streaming request `ReadableStream<Req>` / `AsyncIterable<Req>`
|
|
982
|
+
*
|
|
983
|
+
* It must return an async generator of response messages.
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```ts
|
|
987
|
+
* const events = defineInvokeEventa<Progress | Result, Params>()
|
|
988
|
+
*
|
|
989
|
+
* defineStreamInvokeHandler(serverCtx, events, async function* (payload) {
|
|
990
|
+
* if (isReadableStream<Params>(payload) || isAsyncIterable<Params>(payload)) {
|
|
991
|
+
* for await (const item of payload) {
|
|
992
|
+
* yield { type: 'progress', value: item }
|
|
993
|
+
* }
|
|
994
|
+
* }
|
|
995
|
+
*
|
|
996
|
+
* yield { type: 'result', ok: true }
|
|
997
|
+
* })
|
|
998
|
+
* ```
|
|
999
|
+
*
|
|
1000
|
+
* @param serverCtx Event context on the handler/server side.
|
|
1001
|
+
* @param event Invoke event definition created by `defineInvokeEventa`.
|
|
1002
|
+
* @param fn Stream handler that yields response chunks.
|
|
1003
|
+
*/
|
|
1004
|
+
function defineStreamInvokeHandler(serverCtx, event, fn) {
|
|
1005
|
+
const invokeReceiveEvent = (invokeId) => defineEventa(`${event.receiveEvent.id}-${invokeId}`);
|
|
1006
|
+
const invokeReceiveEventError = (invokeId) => defineEventa(`${event.receiveEventError.id}-${invokeId}`);
|
|
1007
|
+
const invokeReceiveEventStreamEnd = (invokeId) => defineEventa(`${event.receiveEventStreamEnd.id}-${invokeId}`);
|
|
1008
|
+
const streamStates = /* @__PURE__ */ new Map();
|
|
1009
|
+
const abortControllers = /* @__PURE__ */ new Map();
|
|
1010
|
+
const abortReasons = /* @__PURE__ */ new Map();
|
|
1011
|
+
const scheduleAbort = (controller, reason) => {
|
|
1012
|
+
if (typeof queueMicrotask !== "undefined") {
|
|
1013
|
+
queueMicrotask(() => controller.abort(reason));
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
Promise.resolve().then(() => controller.abort(reason));
|
|
1017
|
+
};
|
|
1018
|
+
const handleInvoke = async (invokeId, payload, options) => {
|
|
1019
|
+
const receiveEvent = invokeReceiveEvent(invokeId);
|
|
1020
|
+
const receiveEventError = invokeReceiveEventError(invokeId);
|
|
1021
|
+
const receiveEventStreamEnd = invokeReceiveEventStreamEnd(invokeId);
|
|
1022
|
+
const abortController = new AbortController();
|
|
1023
|
+
abortControllers.set(invokeId, abortController);
|
|
1024
|
+
if (abortReasons.has(invokeId)) scheduleAbort(abortController, abortReasons.get(invokeId));
|
|
1025
|
+
const handlerOptions = options ? {
|
|
1026
|
+
...options,
|
|
1027
|
+
abortController
|
|
1028
|
+
} : { abortController };
|
|
1029
|
+
try {
|
|
1030
|
+
const generator = fn(payload, handlerOptions);
|
|
1031
|
+
for await (const res of generator) serverCtx.emit(receiveEvent, {
|
|
1032
|
+
invokeId,
|
|
1033
|
+
content: res
|
|
1034
|
+
}, options);
|
|
1035
|
+
serverCtx.emit(receiveEventStreamEnd, {
|
|
1036
|
+
invokeId,
|
|
1037
|
+
content: void 0
|
|
1038
|
+
}, options);
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
serverCtx.emit(receiveEventError, {
|
|
1041
|
+
invokeId,
|
|
1042
|
+
content: { error }
|
|
1043
|
+
}, options);
|
|
1044
|
+
} finally {
|
|
1045
|
+
abortControllers.delete(invokeId);
|
|
1046
|
+
abortReasons.delete(invokeId);
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
serverCtx.on(event.sendEvent, async (payload, options) => {
|
|
1050
|
+
if (!payload.body) return;
|
|
1051
|
+
if (!payload.body.invokeId) return;
|
|
1052
|
+
const invokeId = payload.body.invokeId;
|
|
1053
|
+
if (payload.body.isReqStream) {
|
|
1054
|
+
let controller = streamStates.get(invokeId);
|
|
1055
|
+
if (!controller) {
|
|
1056
|
+
let localController;
|
|
1057
|
+
const reqStream = new ReadableStream({ start(c) {
|
|
1058
|
+
localController = c;
|
|
1059
|
+
} });
|
|
1060
|
+
controller = localController;
|
|
1061
|
+
streamStates.set(invokeId, controller);
|
|
1062
|
+
handleInvoke(invokeId, reqStream, options);
|
|
1063
|
+
}
|
|
1064
|
+
controller.enqueue(payload.body.content);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
handleInvoke(invokeId, payload.body.content, options);
|
|
1068
|
+
});
|
|
1069
|
+
serverCtx.on(event.sendEventStreamEnd, (payload) => {
|
|
1070
|
+
if (!payload.body) return;
|
|
1071
|
+
if (!payload.body.invokeId) return;
|
|
1072
|
+
const controller = streamStates.get(payload.body.invokeId);
|
|
1073
|
+
if (!controller) return;
|
|
1074
|
+
controller.close();
|
|
1075
|
+
streamStates.delete(payload.body.invokeId);
|
|
1076
|
+
});
|
|
1077
|
+
serverCtx.on(event.sendEventAbort, (payload) => {
|
|
1078
|
+
if (!payload.body) return;
|
|
1079
|
+
if (!payload.body.invokeId) return;
|
|
1080
|
+
const invokeId = payload.body.invokeId;
|
|
1081
|
+
const reason = payload.body.content;
|
|
1082
|
+
const abortController = abortControllers.get(invokeId);
|
|
1083
|
+
if (!abortController) {
|
|
1084
|
+
abortReasons.set(invokeId, reason);
|
|
1085
|
+
let controller$1 = streamStates.get(invokeId);
|
|
1086
|
+
if (!controller$1) {
|
|
1087
|
+
let localController;
|
|
1088
|
+
const reqStream = new ReadableStream({ start(c) {
|
|
1089
|
+
localController = c;
|
|
1090
|
+
} });
|
|
1091
|
+
controller$1 = localController;
|
|
1092
|
+
streamStates.set(invokeId, controller$1);
|
|
1093
|
+
handleInvoke(invokeId, reqStream);
|
|
1094
|
+
}
|
|
1095
|
+
controller$1.error(createAbortError(reason));
|
|
1096
|
+
streamStates.delete(invokeId);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
scheduleAbort(abortController, reason);
|
|
1100
|
+
const controller = streamStates.get(invokeId);
|
|
1101
|
+
if (controller) {
|
|
1102
|
+
controller.error(createAbortError(reason));
|
|
1103
|
+
streamStates.delete(invokeId);
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Convert a callback-style handler into a stream handler.
|
|
1109
|
+
*
|
|
1110
|
+
* Use `emit` to push response chunks, and return when done.
|
|
1111
|
+
* Works for unary or streaming requests.
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* ```ts
|
|
1115
|
+
* defineStreamInvokeHandler(ctx, events, toStreamHandler(async ({ payload, emit }) => {
|
|
1116
|
+
* if (isReadableStream<Params>(payload) || isAsyncIterable<Params>(payload)) {
|
|
1117
|
+
* for await (const item of payload) {
|
|
1118
|
+
* emit({ type: 'progress', value: item })
|
|
1119
|
+
* }
|
|
1120
|
+
*
|
|
1121
|
+
* emit({ type: 'result', ok: true })
|
|
1122
|
+
* return
|
|
1123
|
+
* }
|
|
1124
|
+
*
|
|
1125
|
+
* emit({ type: 'result', ok: true })
|
|
1126
|
+
* }))
|
|
1127
|
+
* ```
|
|
1128
|
+
*
|
|
1129
|
+
* @param handler Callback handler with `emit` for streaming responses.
|
|
1130
|
+
*/
|
|
1131
|
+
function toStreamHandler(handler) {
|
|
1132
|
+
return (payload, options) => {
|
|
1133
|
+
const values = [];
|
|
1134
|
+
let resolve;
|
|
1135
|
+
let handlerError = null;
|
|
1136
|
+
values.push(new Promise((r) => {
|
|
1137
|
+
resolve = r;
|
|
1138
|
+
}));
|
|
1139
|
+
const emit = (data) => {
|
|
1140
|
+
resolve([data, false]);
|
|
1141
|
+
values.push(new Promise((r) => {
|
|
1142
|
+
resolve = r;
|
|
1143
|
+
}));
|
|
1144
|
+
};
|
|
1145
|
+
handler({
|
|
1146
|
+
payload,
|
|
1147
|
+
options,
|
|
1148
|
+
emit
|
|
1149
|
+
}).then(() => {
|
|
1150
|
+
resolve([void 0, true]);
|
|
1151
|
+
}).catch((err) => {
|
|
1152
|
+
handlerError = err;
|
|
1153
|
+
resolve([void 0, true]);
|
|
1154
|
+
});
|
|
1155
|
+
return async function* () {
|
|
1156
|
+
let val;
|
|
1157
|
+
for (let i = 0, done = false; !done; i++) {
|
|
1158
|
+
[val, done] = await values[i];
|
|
1159
|
+
delete values[i];
|
|
1160
|
+
if (handlerError) throw handlerError;
|
|
1161
|
+
if (!done) yield val;
|
|
1162
|
+
}
|
|
1163
|
+
}();
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
//#endregion
|
|
1168
|
+
export { isInvokeEventa as C, registerInvokeAbortEventListeners as E, defineInvokeEventa as S, isSendEvent as T, isAbortError as _, withRemoteMethods as a, randomBetween as b, defineInvokeHandler as c, isExtendableInvokeResponseLike as d, undefineInvokeHandler as f, createUntilTriggeredOnce as g, createUntilTriggered as h, createRemoteMethodTagPrefix as i, defineInvokeHandlers as l, createUntil as m, defineStreamInvokeHandler as n, withTransfer as o, createAbortError as p, toStreamHandler as r, defineInvoke as s, defineStreamInvoke as t, defineInvokes as u, isAsyncIterable as v, isReceiveEvent as w, InvokeEventType as x, isReadableStream as y };
|
|
1169
|
+
//# sourceMappingURL=src-DZ7si0kE.mjs.map
|