-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathINodeJSService.cs
326 lines (313 loc) · 33.4 KB
/
INodeJSService.cs
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Jering.Javascript.NodeJS
{
/// <summary>
/// An abstraction for invoking code in NodeJS.
/// </summary>
public interface INodeJSService : IDisposable
{
/// <summary>Invokes a function from a NodeJS module on disk.</summary>
/// <typeparam name="T">The type of value returned. This may be a JSON-serializable type, <see cref="string"/>, or <see cref="Stream"/>.</typeparam>
/// <param name="modulePath">The path to the module relative to <see cref="NodeJSProcessOptions.ProjectPath"/>. This value must not be <c>null</c>, whitespace or an empty string.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="modulePath"/> is <c>null</c>, whitespace or an empty string.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>To avoid rereads and recompilations on subsequent invocations, NodeJS caches the module using the its absolute path as cache identifier.</para>
/// </remarks>
/// <example>
/// If we have a file named exampleModule.js (located in <c>NodeJSProcessOptions.ProjectPath</c>), with contents:
/// <code language="javascript">module.exports = (callback, message) => callback(null, { resultMessage: message });</code>
/// Using the class <c>Result</c>:
/// <code language="csharp">public class Result
/// {
/// public string? Message { get; set; }
/// }</code>
/// The following assertion will pass:
/// <code language="csharp">Result? result = await nodeJSService.InvokeFromFileAsync<Result>("exampleModule.js", args: new[] { "success" });
///
/// Assert.Equal("success", result?.Message);</code>
/// </example>
Task<T?> InvokeFromFileAsync<T>(string modulePath, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module on disk.</summary>
/// <param name="modulePath">The path to the module relative to <see cref="NodeJSProcessOptions.ProjectPath"/>. This value must not be <c>null</c>, whitespace or an empty string.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="modulePath"/> is <c>null</c>, whitespace or an empty string.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>To avoid rereads and recompilations on subsequent invocations, NodeJS caches the module using the its absolute path as cache identifier. </para>
/// </remarks>
Task InvokeFromFileAsync(string modulePath, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in string form.</summary>
/// <typeparam name="T">The type of value returned. This may be a JSON-serializable type, <see cref="string"/>, or <see cref="Stream"/>.</typeparam>
/// <param name="moduleString">The module in string form. This value must not be <c>null</c>, whitespace or an empty string.</param>
/// <param name="cacheIdentifier">The module's cache identifier. If this value is <c>null</c>, NodeJS ignores its module cache..</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleString"/> is <c>null</c>, whitespace or an empty string.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>If <paramref name="cacheIdentifier"/> is <c>null</c>, sends <paramref name="moduleString"/> to NodeJS where it's compiled it for one-time use.</para>
/// <para>If <paramref name="cacheIdentifier"/> isn't <c>null</c>, sends both <paramref name="moduleString"/> and <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it compiles and caches the module.</para>
/// <para>Once the module is cached, you may use <see cref="TryInvokeFromCacheAsync{T}"/> to invoke directly from the cache, avoiding the overhead of sending <paramref name="moduleString"/>.</para>
/// </remarks>
/// <example>
/// Using the class <c>Result</c>:
/// <code language="csharp">public class Result
/// {
/// public string? Message { get; set; }
/// }</code>
/// The following assertion will pass:
/// <code language="csharp">Result? result = await nodeJSService.InvokeFromStringAsync<Result>("module.exports = (callback, message) => callback(null, { resultMessage: message });",
/// args: new[] { "success" });
///
/// Assert.Equal("success", result?.Message);</code>
/// </example>
Task<T?> InvokeFromStringAsync<T>(string moduleString, string? cacheIdentifier = null, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in string form.</summary>
/// <param name="moduleString">The module in string form. This value must not be <c>null</c>, whitespace or an empty string.</param>
/// <param name="cacheIdentifier">The module's cache identifier. If this value is <c>null</c>, NodeJS ignores its module cache..</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleString"/> is <c>null</c>, whitespace or an empty string.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>If <paramref name="cacheIdentifier"/> is <c>null</c>, sends <paramref name="moduleString"/> to NodeJS where it's compiled for one-time use.</para>
/// <para>If <paramref name="cacheIdentifier"/> isn't <c>null</c>, sends both <paramref name="moduleString"/> and <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it compiles and caches the module.</para>
/// <para>Once the module is cached, you may use <see cref="TryInvokeFromCacheAsync{T}"/> to invoke directly from the cache, avoiding the overhead of sending <paramref name="moduleString"/>.</para>
/// </remarks>
Task InvokeFromStringAsync(string moduleString, string? cacheIdentifier = null, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in string form.</summary>
/// <typeparam name="T">The type of value returned. This may be a JSON-serializable type, <see cref="string"/>, or <see cref="Stream"/>.</typeparam>
/// <param name="moduleFactory">The factory that creates the module string. This value must not be <c>null</c> and it must not return <c>null</c>, whitespace or an empty string.</param>
/// <param name="cacheIdentifier">The module's cache identifier. This value must not be <c>null</c>.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown if module is not cached but <paramref name="moduleFactory"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheIdentifier"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleFactory"/> returns <c>null</c>, whitespace or an empty string.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>Initially, sends only <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it informs the .NET process that the module isn't cached.
/// The .NET process then creates the module string using <paramref name="moduleFactory"/> and send it to NodeJS where it's compiled, invoked and cached.</para>
/// <para>If <paramref name="exportName"/> is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked. Otherwise, invokes the function named <paramref name="exportName"/> in <c>module.exports</c>.</para>
/// </remarks>
Task<T?> InvokeFromStringAsync<T>(Func<string> moduleFactory, string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in string form.</summary>
/// <param name="moduleFactory">The factory that creates the module string. This value must not be <c>null</c> and it must not return <c>null</c>, whitespace or an empty string.</param>
/// <param name="cacheIdentifier">The module's cache identifier. This value must not be <c>null</c>.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown if module is not cached but <paramref name="moduleFactory"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheIdentifier"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleFactory"/> returns <c>null</c>, whitespace or an empty string.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>Initially, sends only <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it informs the .NET process that the module isn't cached.
/// The .NET process then creates the module string using <paramref name="moduleFactory"/> and send it to NodeJS where it's compiled, invoked and cached.</para>
/// <para>If <paramref name="exportName"/> is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked. Otherwise, invokes the function named <paramref name="exportName"/> in <c>module.exports</c>.</para>
/// </remarks>
Task InvokeFromStringAsync(Func<string> moduleFactory, string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in stream form.</summary>
/// <typeparam name="T">The type of value returned. This may be a JSON-serializable type, <see cref="string"/>, or <see cref="Stream"/>.</typeparam>
/// <param name="moduleStream">The module in stream form. This value must not be <c>null</c>.</param>
/// <param name="cacheIdentifier">The module's cache identifier. If this value is <c>null</c>, NodeJS ignores its module cache..</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleStream"/> is <c>null</c>.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>If <paramref name="cacheIdentifier"/> is <c>null</c>, sends the stream to NodeJS where it's compiled for one-time use.</para>
/// <para>If <paramref name="cacheIdentifier"/> isn't <c>null</c>, sends both the stream and <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it compiles and caches the module.</para>
/// <para>Once the module is cached, you may use <see cref="TryInvokeFromCacheAsync{T}"/> to invoke directly from the cache, avoiding the overhead of sending the module stream.</para>
/// </remarks>
/// <example>
/// Using the class <c>Result</c>:
/// <code language="csharp">public class Result
/// {
/// public string? Message { get; set; }
/// }</code>
/// The following assertion will pass:
/// <code language="csharp">using (var memoryStream = new MemoryStream())
/// using (var streamWriter = new StreamWriter(memoryStream))
/// {
/// // Write the module to a MemoryStream for demonstration purposes.
/// streamWriter.Write("module.exports = (callback, message) => callback(null, {resultMessage: message});");
/// streamWriter.Flush();
/// memoryStream.Position = 0;
///
/// Result? result = await nodeJSService.InvokeFromStreamAsync<Result>(memoryStream, args: new[] { "success" });
///
/// Assert.Equal("success", result?.Message);
/// }</code>
/// </example>
Task<T?> InvokeFromStreamAsync<T>(Stream moduleStream, string? cacheIdentifier = null, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in stream form.</summary>
/// <param name="moduleStream">The module in stream form. This value must not be <c>null</c>.</param>
/// <param name="cacheIdentifier">The module's cache identifier. If this value is <c>null</c>, NodeJS ignores its module cache..</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleStream"/> is <c>null</c>.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>If <paramref name="cacheIdentifier"/> is <c>null</c>, sends the stream to NodeJS where it's compiled for one-time use.</para>
/// <para>If <paramref name="cacheIdentifier"/> isn't <c>null</c>, sends both the stream and <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it compiles and caches the module.</para>
/// <para>Once the module is cached, you may use <see cref="TryInvokeFromCacheAsync{T}"/> to invoke directly from the cache, avoiding the overhead of sending the module stream.</para>
/// </remarks>
Task InvokeFromStreamAsync(Stream moduleStream, string? cacheIdentifier = null, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in stream form.</summary>
/// <typeparam name="T">The type of value returned. This may be a JSON-serializable type, <see cref="string"/>, or <see cref="Stream"/>.</typeparam>
/// <param name="moduleFactory">The factory that creates the module stream. This value must not be <c>null</c> and it must not return <c>null</c>.</param>
/// <param name="cacheIdentifier">The module's cache identifier. This value must not be <c>null</c>.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown if module is not cached but <paramref name="moduleFactory"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheIdentifier"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleFactory"/> returns <c>null</c>.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>Initially, sends only <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it informs the .NET process that the module isn't cached.
/// The .NET process then creates the module stream using <paramref name="moduleFactory"/> and send it to NodeJS where it's compiled, invoked and cached.</para>
/// <para>If <paramref name="exportName"/> is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked. Otherwise, invokes the function named <paramref name="exportName"/> in <c>module.exports</c>.</para>
/// </remarks>
Task<T?> InvokeFromStreamAsync<T>(Func<Stream> moduleFactory, string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Invokes a function from a NodeJS module in stream form.</summary>
/// <param name="moduleFactory">The factory that creates the module stream. This value must not be <c>null</c> and it must not return <c>null</c>.</param>
/// <param name="cacheIdentifier">The module's cache identifier. This value must not be <c>null</c>.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">Thrown if module is not cached but <paramref name="moduleFactory"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheIdentifier"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="moduleFactory"/> returns <c>null</c>.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <remarks>
/// <para>Initially, sends only <paramref name="cacheIdentifier"/> to NodeJS. NodeJS reuses the module if it's already cached. Otherwise, it informs the .NET process that the module isn't cached.
/// The .NET process then creates the module stream using <paramref name="moduleFactory"/> and send it to NodeJS where it's compiled, invoked and cached.</para>
/// <para>If <paramref name="exportName"/> is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked. Otherwise, invokes the function named <paramref name="exportName"/> in <c>module.exports</c>.</para>
/// </remarks>
Task InvokeFromStreamAsync(Func<Stream> moduleFactory, string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Attempts to invoke a function from a module in NodeJS's cache.</summary>
/// <typeparam name="T">The type of value returned. This may be a JSON-serializable type, <see cref="string"/>, or <see cref="Stream"/>.</typeparam>
/// <param name="cacheIdentifier">The module's cache identifier. This value must not be <c>null</c>.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation. On completion, the task returns a (bool, T) with the bool set to true on
/// success and false otherwise.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheIdentifier"/> is <c>null</c>.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
/// <example>
/// Using the class <c>Result</c>:
/// <code language="csharp">public class Result
/// {
/// public string? Message { get; set; }
/// }</code>
/// The following assertion will pass:
/// <code language="csharp">// Cache the module
/// string cacheIdentifier = "exampleModule";
/// await nodeJSService.InvokeFromStringAsync<Result>("module.exports = (callback, message) => callback(null, { resultMessage: message });",
/// cacheIdentifier,
/// args: new[] { "success" });
///
/// // Invoke from cache
/// (bool success, Result? result) = await nodeJSService.TryInvokeFromCacheAsync<Result>(cacheIdentifier, args: new[] { "success" });
///
/// Assert.True(success);
/// Assert.Equal("success", result?.Message);</code>
/// </example>
Task<(bool, T?)> TryInvokeFromCacheAsync<T>(string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Attempts to invoke a function from a module in NodeJS's cache.</summary>
/// <param name="cacheIdentifier">The module's cache identifier. This value must not be <c>null</c>.</param>
/// <param name="exportName">The name of the function in <c>module.exports</c> to invoke. If this value is <c>null</c>, <c>module.exports</c> is assumed to be a function and is invoked.</param>
/// <param name="args">The sequence of JSON-serializable arguments to pass to the function to invoke. If this value is <c>null</c>, no arguments are passed.</param>
/// <param name="cancellationToken">The cancellation token for the asynchronous operation.</param>
/// <returns>The <see cref="Task"/> representing the asynchronous operation. On completion, the task returns true on success and false otherwise.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheIdentifier"/> is <c>null</c>.</exception>
/// <exception cref="ConnectionException">Thrown if unable to connect to NodeJS.</exception>
/// <exception cref="InvocationException">Thrown if the invocation request times out.</exception>
/// <exception cref="InvocationException">Thrown if a NodeJS error occurs.</exception>
/// <exception cref="ObjectDisposedException">Thrown if this instance is disposed or if it attempts to use a disposed dependency.</exception>
/// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is cancelled.</exception>
Task<bool> TryInvokeFromCacheAsync(string cacheIdentifier, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default);
/// <summary>Moves subsequent invocations to a new NodeJS process.</summary>
/// <returns>The <see cref="ValueTask"/> representing the asynchronous operation.</returns>
/// <remarks>
/// <para>This method exposes the system used by file watching (see <see cref="OutOfProcessNodeJSServiceOptions.EnableFileWatching"/>) and process retries
/// (see <see cref="OutOfProcessNodeJSServiceOptions.NumProcessRetries"/>) to move to new processes.</para>
/// <para>When is access to this system useful? Consider the situation where your application uses file watching.
/// If your application knows when files change (e.g. your application is the actor changing files) you can manually invoke this method instead of using file
/// watching. This enables you to avoid the overhead of file watching.</para>
/// <para>You do not need to await this method. Subsequent invocations are wait asynchronously until the new process is ready.</para>
/// <para>The method respects <see cref="OutOfProcessNodeJSServiceOptions.GracefulProcessShutdown"/>.</para>
/// </remarks>
ValueTask MoveToNewProcessAsync();
}
}