@nativescript/windows 0.1.0-alpha.6 → 0.1.0-alpha.61

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.
@@ -1,353 +1,240 @@
1
1
  using System;
2
- using System.Collections;
3
2
  using System.Collections.Concurrent;
4
3
  using System.Collections.Generic;
4
+ using System.IO;
5
5
  using System.Linq;
6
6
  using System.Reflection;
7
7
  using System.Runtime.InteropServices;
8
+ using System.Runtime.Loader;
8
9
  using System.Text.Json;
9
- using System.Text.Json.Nodes;
10
- using System.Threading;
11
- using System.Threading.Tasks;
10
+
11
+ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeTests")]
12
+ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeBenchmarks")]
12
13
 
13
14
  namespace NativeScriptBridge;
14
15
 
15
- /// <summary>
16
- /// Reflection-based .NET dispatch bridge — UTF-8 ABI edition.
17
- ///
18
- /// Invoke() receives a UTF-8 byte slice (no string allocation on the Rust side),
19
- /// dispatches via reflection, and serialises the response directly to a UTF-8
20
- /// byte array via JsonSerializer.SerializeToUtf8Bytes(). This removes the
21
- /// UTF-16 encode/decode round-trip of the original char* ABI.
22
- ///
23
- /// Request JSON schema
24
- /// -------------------
25
- /// Static call: { "assembly": "System", "typeName": "System.Diagnostics.Stopwatch", "method": "StartNew", "args": [] }
26
- /// Constructor: { "assembly": "System", "typeName": "System.Text.StringBuilder", "method": ".ctor", "args": [128] }
27
- /// Instance call: { "handle": 3, "method": "Stop", "args": [] }
28
- /// Property get: { "assembly": "...", "typeName": "...", "method": "get_Now", "args": [] }
29
- /// Property set: { "handle": 3, "method": "set_IsEnabled", "args": [true] }
30
- /// Release: { "handle": 3, "method": "__release", "args": [] }
31
- ///
32
- /// Response JSON schema
33
- /// --------------------
34
- /// Primitive / string: { "result": 42.5 }
35
- /// Managed object: { "result": { "__handle": 7, "__type": "System.Diagnostics.Stopwatch" } }
36
- /// Array / enumerable: { "result": [1, 2, 3] }
37
- /// Error: { "error": "Method not found" }
38
- /// </summary>
39
- public static class Bridge
16
+ public static partial class Bridge
40
17
  {
41
- private static readonly ConcurrentDictionary<int, object?> s_handles = new();
42
- private static int s_nextHandle;
43
-
44
- private static readonly ConcurrentDictionary<string, Type?> s_typeCache = new();
45
- private static readonly ConcurrentDictionary<string, MethodInfo?> s_methodCache = new();
46
- private static readonly ConcurrentDictionary<string, PropertyInfo?> s_propertyCache = new();
47
-
48
- // ── exported entry points ────────────────────────────────────────────────
49
-
50
- /// Called by the Rust runtime. Request arrives as a raw UTF-8 byte slice;
51
- /// response is written as a UTF-8 byte buffer allocated with Marshal.AllocHGlobal.
52
- [UnmanagedCallersOnly(EntryPoint = "Invoke",
53
- CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
54
- public static unsafe int Invoke(
55
- byte* requestPtr, int requestLen,
56
- byte** responsePtr, int* responseLenPtr)
18
+ internal static readonly ConcurrentDictionary<int, object?> s_handles = new();
19
+ internal static int s_nextHandle;
20
+
21
+ // Function pointer registered by the Rust runtime so managed delegates can
22
+ // call back into V8 without a JSON round-trip.
23
+ internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
24
+ s_jsInvoker;
25
+
26
+ private static readonly ConcurrentDictionary<string, Type?> s_typeCache
27
+ = new(StringComparer.Ordinal);
28
+ private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
29
+ private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
30
+ private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
31
+ // Cache of attempted assembly loads (simple name -> Assembly or null if not found).
32
+ private static readonly ConcurrentDictionary<string, Assembly?> s_assemblyLoadCache
33
+ = new(StringComparer.OrdinalIgnoreCase);
34
+ // Directories to search for assemblies (initialized once in static ctor).
35
+ private static readonly string[] s_assemblySearchDirs;
36
+
37
+ private static readonly JsonSerializerOptions s_coerceOpts = new()
57
38
  {
58
- byte[] responseBytes;
59
- try
60
- {
61
- var requestSpan = new ReadOnlySpan<byte>(requestPtr, requestLen);
62
- var req = JsonSerializer.Deserialize<InvokeRequest>(requestSpan, JsonOptions.Default)!;
63
- var result = Dispatch(req);
64
- responseBytes = JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions.Default);
65
- }
66
- catch (Exception ex)
67
- {
68
- var errResult = new InvokeResult(null, Unwrap(ex).Message);
69
- responseBytes = JsonSerializer.SerializeToUtf8Bytes(errResult, JsonOptions.Default);
70
- }
71
-
72
- WriteResponse(responseBytes, responsePtr, responseLenPtr);
73
- return 0;
74
- }
39
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
40
+ };
75
41
 
76
- /// Frees the response buffer allocated by Invoke.
77
- [UnmanagedCallersOnly(EntryPoint = "Free",
78
- CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
79
- public static unsafe void Free(byte* ptr)
42
+ internal static void ClearCaches()
80
43
  {
81
- if (ptr != null)
82
- Marshal.FreeHGlobal((IntPtr)ptr);
44
+ s_typeCache.Clear();
45
+ s_methodCache.Clear();
46
+ s_propCache.Clear();
47
+ s_ctorCache.Clear();
48
+ s_handles.Clear();
49
+ s_nextHandle = 0;
83
50
  }
84
51
 
85
- // ── dispatch ─────────────────────────────────────────────────────────────
86
-
87
- private static InvokeResult Dispatch(InvokeRequest req)
52
+ // Returns a JSON object mapping top-level namespace roots (e.g. "NativeScript")
53
+ // to the assembly simple-name that most likely contains that namespace's types.
54
+ // Reuses s_assemblySearchDirs so plugin and NuGet assemblies are included.
55
+ public static string GetNamespaceAssemblyMapJson()
88
56
  {
89
- if (req.Method == "__release" && req.Handle.HasValue)
90
- {
91
- s_handles.TryRemove(req.Handle.Value, out _);
92
- return new InvokeResult(null, null);
93
- }
94
-
95
- // Return reflection metadata so JS can distinguish methods from properties.
96
- if (req.Method == "__members__")
57
+ var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
58
+ try
97
59
  {
98
- var t = req.Handle.HasValue
99
- ? (s_handles.TryGetValue(req.Handle.Value, out var h) ? h?.GetType() : null)
100
- : ResolveType(req.Assembly, req.TypeName);
101
- if (t is null) return new InvokeResult(null, $"Type not found: {req.TypeName}");
102
- var inst = BindingFlags.Public | BindingFlags.Instance;
103
- var stat = BindingFlags.Public | BindingFlags.Static;
104
- var members = new
60
+ foreach (var dir in s_assemblySearchDirs.Where(Directory.Exists))
105
61
  {
106
- methods = t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
107
- properties = t.GetProperties(inst).Select(p => p.Name).Distinct().ToArray(),
108
- staticMethods = t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
109
- staticProperties = t.GetProperties(stat).Select(p => p.Name).Distinct().ToArray(),
110
- };
111
- return new InvokeResult(JsonSerializer.SerializeToElement(members, JsonOptions.Default), null);
112
- }
113
-
114
- object? target = null;
115
- Type? type = null;
116
-
117
- if (req.Handle.HasValue)
118
- {
119
- if (!s_handles.TryGetValue(req.Handle.Value, out target))
120
- return new InvokeResult(null, $"Invalid handle {req.Handle.Value}");
121
- type = target?.GetType();
122
- }
123
- else
124
- {
125
- type = ResolveType(req.Assembly, req.TypeName);
126
- if (type is null)
127
- return new InvokeResult(null, $"Type not found: {req.TypeName}");
62
+ try
63
+ {
64
+ foreach (var file in Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly))
65
+ {
66
+ try
67
+ {
68
+ var simple = Path.GetFileNameWithoutExtension(file);
69
+ if (string.IsNullOrEmpty(simple)) continue;
70
+
71
+ Assembly asm = AppDomain.CurrentDomain.GetAssemblies()
72
+ .FirstOrDefault(a => string.Equals(a.GetName().Name, simple, StringComparison.OrdinalIgnoreCase));
73
+ if (asm is null)
74
+ {
75
+ try { asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); }
76
+ catch { continue; }
77
+ }
78
+
79
+ Type[] types;
80
+ try { types = asm.GetExportedTypes(); }
81
+ catch { types = Array.Empty<Type>(); }
82
+
83
+ foreach (var t in types)
84
+ {
85
+ if (string.IsNullOrEmpty(t.Namespace)) continue;
86
+ var root = t.Namespace.Split('.')[0];
87
+ if (!map.ContainsKey(root)) map[root] = asm.GetName().Name;
88
+ }
89
+ }
90
+ catch { }
91
+ }
92
+ }
93
+ catch { }
94
+ }
128
95
  }
96
+ catch { }
97
+ try { return JsonSerializer.Serialize(map); } catch { return "{}"; }
98
+ }
129
99
 
130
- if (type is null)
131
- return new InvokeResult(null, "Cannot determine target type");
132
-
133
- var method = req.Method ?? throw new ArgumentException("Method is required");
134
-
135
- // ── Constructor ──────────────────────────────────────────────────────
136
- if (method == ".ctor")
100
+ static Bridge()
101
+ {
102
+ try
137
103
  {
138
- var argElements = req.Args ?? [];
139
- var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
140
- var ctor = ctors.FirstOrDefault(c => c.GetParameters().Length == argElements.Count)
141
- ?? ctors.FirstOrDefault();
142
- if (ctor is null)
143
- return new InvokeResult(null, $"No public constructor found on {type.FullName}");
144
- var pars = ctor.GetParameters();
145
- var ctorArgs = new object?[pars.Length];
146
- for (int i = 0; i < pars.Length && i < argElements.Count; i++)
147
- ctorArgs[i] = Coerce(argElements[i], pars[i].ParameterType);
148
- return Box(ctor.Invoke(ctorArgs));
149
- }
150
-
151
- var isStatic = target is null;
152
- var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance)
153
- | BindingFlags.Public;
104
+ var baseDir = AppContext.BaseDirectory ?? AppDomain.CurrentDomain.BaseDirectory;
105
+ var dirs = new List<string> { baseDir };
154
106
 
155
- // ── Property getter / setter ─────────────────────────────────────────
156
- if (method.StartsWith("get_", StringComparison.Ordinal))
157
- {
158
- var prop = GetCachedProperty(type, method[4..], flags);
159
- if (prop is not null)
160
- return Box(prop.GetValue(target));
161
- }
162
- if (method.StartsWith("set_", StringComparison.Ordinal) && req.Args?.Count == 1)
163
- {
164
- var prop = GetCachedProperty(type, method[4..], flags);
165
- if (prop is not null)
107
+ // libs/ subtree relative to the bridge's own directory.
108
+ var libs = Path.Combine(baseDir, "libs");
109
+ if (Directory.Exists(libs))
166
110
  {
167
- prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
168
- return new InvokeResult(null, null);
111
+ dirs.Add(libs);
112
+ try { dirs.AddRange(Directory.GetDirectories(libs, "*", SearchOption.AllDirectories)); } catch { }
169
113
  }
170
- }
171
-
172
- // ── Regular method ───────────────────────────────────────────────────
173
- var argElems = req.Args ?? [];
174
- var mi = FindMethod(type, method, argElems.Count, flags);
175
- if (mi is null)
176
- return new InvokeResult(null,
177
- $"Method '{method}' ({argElems.Count} args) not found on {type.FullName}");
178
114
 
179
- var parameters = mi.GetParameters();
180
- var callArgs = new object?[parameters.Length];
181
- for (int i = 0; i < parameters.Length && i < argElems.Count; i++)
182
- callArgs[i] = Coerce(argElems[i], parameters[i].ParameterType);
183
-
184
- return Box(AwaitIfTask(mi.Invoke(target, callArgs)));
185
- }
186
-
187
- // ── helpers ───────────────────────────────────────────────────────────────
188
-
189
- private static object? AwaitIfTask(object? value)
190
- {
191
- if (value is null) return null;
192
- var t = value.GetType();
115
+ // The bridge DLL lives at dotnet-bridge/publish/ inside the app output root.
116
+ // Plugin assemblies (added via plugin.props) land at plugins/**/*.dll and NuGet
117
+ // assemblies land at the app output root itself — both outside the bridge subtree.
118
+ // Use the host process directory to reach them.
119
+ try
120
+ {
121
+ var processDir = Path.GetDirectoryName(
122
+ System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName);
123
+ if (!string.IsNullOrEmpty(processDir))
124
+ {
125
+ dirs.Add(processDir);
126
+
127
+ // plugins/ subtree: CLI-managed plugin DLLs live here.
128
+ // Check both {processDir}/plugins and {processDir}/../plugins because
129
+ // the NS CLI places plugins/ as a sibling of bin/, not inside it.
130
+ var pluginsDir = Path.Combine(processDir, "plugins");
131
+ if (Directory.Exists(pluginsDir))
132
+ try { dirs.AddRange(Directory.GetDirectories(pluginsDir, "*", SearchOption.AllDirectories)); } catch { }
133
+
134
+ var parentDir = Path.GetDirectoryName(processDir);
135
+ if (!string.IsNullOrEmpty(parentDir))
136
+ {
137
+ var parentPlugins = Path.Combine(parentDir, "plugins");
138
+ if (Directory.Exists(parentPlugins))
139
+ {
140
+ dirs.Add(parentPlugins);
141
+ try { dirs.AddRange(Directory.GetDirectories(parentPlugins, "*", SearchOption.AllDirectories)); } catch { }
142
+ }
143
+ }
144
+
145
+ // libs/ relative to the app root (alternative convention).
146
+ var processLibs = Path.Combine(processDir, "libs");
147
+ if (Directory.Exists(processLibs))
148
+ try { dirs.AddRange(Directory.GetDirectories(processLibs, "*", SearchOption.AllDirectories)); } catch { }
149
+ }
150
+ }
151
+ catch { }
193
152
 
194
- if (value is Task task)
195
- {
196
- task.Wait();
197
- if (task.IsFaulted)
198
- throw task.Exception!.InnerException ?? task.Exception;
199
- return t.GetProperty("Result")?.GetValue(value);
153
+ s_assemblySearchDirs = dirs.Where(d => !string.IsNullOrEmpty(d)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
154
+ AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
200
155
  }
201
-
202
- if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>))
156
+ catch
203
157
  {
204
- var innerTask = (Task)t.GetMethod("AsTask")!.Invoke(value, null)!;
205
- innerTask.Wait();
206
- if (innerTask.IsFaulted)
207
- throw innerTask.Exception!.InnerException ?? innerTask.Exception;
208
- return innerTask.GetType().GetProperty("Result")?.GetValue(innerTask);
158
+ s_assemblySearchDirs = Array.Empty<string>();
209
159
  }
210
-
211
- if (value is System.Threading.Tasks.ValueTask vt)
212
- {
213
- vt.AsTask().Wait();
214
- return null;
215
- }
216
-
217
- return value;
218
160
  }
219
161
 
220
- private static Type? ResolveType(string? assemblyName, string? typeName)
162
+ private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
221
163
  {
222
- if (string.IsNullOrEmpty(typeName)) return null;
223
- var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
224
- return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
225
- }
164
+ try
165
+ {
166
+ var requested = new AssemblyName(args.Name).Name;
167
+ if (string.IsNullOrEmpty(requested)) return null;
226
168
 
227
- private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
228
- {
229
- var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
230
- var t = Type.GetType(fqn);
231
- if (t is not null) return t;
169
+ if (s_assemblyLoadCache.TryGetValue(requested, out var cached)) return cached;
232
170
 
233
- foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
234
- {
235
- t = asm.GetType(typeName!);
236
- if (t is not null) return t;
237
- }
171
+ // Check already-loaded assemblies first (avoid recursive Assembly.Load).
172
+ var loadedAsm = AppDomain.CurrentDomain.GetAssemblies()
173
+ .FirstOrDefault(a => string.Equals(a.GetName().Name, requested, StringComparison.OrdinalIgnoreCase));
174
+ if (loadedAsm is not null)
175
+ {
176
+ s_assemblyLoadCache[requested] = loadedAsm;
177
+ return loadedAsm;
178
+ }
238
179
 
239
- if (!string.IsNullOrEmpty(assemblyName))
240
- {
241
- try
180
+ // Try to locate a matching dll in known search directories.
181
+ foreach (var dir in s_assemblySearchDirs)
242
182
  {
243
- var asm = Assembly.Load(assemblyName);
244
- t = asm.GetType(typeName!);
245
- if (t is not null) return t;
183
+ try
184
+ {
185
+ var candidate = Path.Combine(dir, requested + ".dll");
186
+ if (File.Exists(candidate))
187
+ {
188
+ try
189
+ {
190
+ var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate);
191
+ s_assemblyLoadCache[requested] = asm;
192
+ return asm;
193
+ }
194
+ catch { }
195
+ }
196
+ }
197
+ catch { }
246
198
  }
247
- catch { }
248
- }
249
199
 
200
+ s_assemblyLoadCache[requested] = null;
201
+ }
202
+ catch { }
250
203
  return null;
251
204
  }
252
205
 
253
- private static MethodInfo? FindMethod(Type type, string name, int argCount, BindingFlags flags)
254
- {
255
- var key = $"{type.FullName}|{name}|{argCount}|{(int)flags}";
256
- return s_methodCache.GetOrAdd(key, _ => FindMethodCore(type, name, argCount, flags));
257
- }
258
-
259
- private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
260
- {
261
- var match = Array.Find(
262
- type.GetMethods(flags),
263
- m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
264
- if (match is not null) return match;
265
- return type.GetMethod(name, flags);
266
- }
267
-
268
- private static PropertyInfo? GetCachedProperty(Type type, string name, BindingFlags flags)
269
- {
270
- var key = $"{type.FullName}|{name}|{(int)flags}";
271
- return s_propertyCache.GetOrAdd(key, _ => type.GetProperty(name, flags));
272
- }
273
-
274
- private static object? Coerce(JsonElement el, Type targetType)
206
+ [UnmanagedCallersOnly(EntryPoint = "RegisterJsCallback",
207
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
208
+ public static unsafe int RegisterJsCallback(
209
+ delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void> callback)
275
210
  {
276
- if (el.ValueKind == JsonValueKind.Null) return null;
277
- if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
278
- {
279
- s_handles.TryGetValue(h.GetInt32(), out var obj);
280
- return obj;
281
- }
282
- return JsonSerializer.Deserialize(el.GetRawText(), targetType, JsonOptions.Default);
211
+ s_jsInvoker = callback;
212
+ return 0;
283
213
  }
284
214
 
285
- private static InvokeResult Box(object? value)
215
+ [UnmanagedCallersOnly(EntryPoint = "Invoke",
216
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
217
+ public static unsafe int Invoke(
218
+ byte* requestPtr, int requestLen,
219
+ byte** responsePtr, int* responseLenPtr)
286
220
  {
287
- if (value is null) return new InvokeResult(null, null);
288
-
289
- var t = value.GetType();
290
-
291
- if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
292
- || t == typeof(DateTime) || t == typeof(DateTimeOffset)
293
- || t == typeof(TimeSpan) || t == typeof(Guid))
221
+ try
294
222
  {
295
- return new InvokeResult(
296
- JsonSerializer.SerializeToElement(value, t, JsonOptions.Default), null);
223
+ var span = new ReadOnlySpan<byte>(requestPtr, requestLen);
224
+ var req = JsonSerializer.Deserialize(span, BridgeJsonContext.Default.InvokeRequest)!;
225
+ WriteResult(Dispatch(req), responsePtr, responseLenPtr);
297
226
  }
298
-
299
- // Arrays and IEnumerable — serialise as JSON arrays.
300
- if (t.IsArray || (t != typeof(string) && value is IEnumerable enumerable))
227
+ catch (Exception ex)
301
228
  {
302
- try
303
- {
304
- var list = new List<object?>();
305
- foreach (var item in (IEnumerable)value) list.Add(item);
306
- return new InvokeResult(
307
- JsonSerializer.SerializeToElement(list, typeof(List<object?>), JsonOptions.Default),
308
- null);
309
- }
310
- catch { }
229
+ WriteError(Unwrap(ex).Message, responsePtr, responseLenPtr);
311
230
  }
312
-
313
- var id = Interlocked.Increment(ref s_nextHandle);
314
- s_handles[id] = value;
315
- return new InvokeResult(
316
- JsonSerializer.SerializeToElement(
317
- new { __handle = id, __type = t.FullName }, JsonOptions.Default),
318
- null);
231
+ return 0;
319
232
  }
320
233
 
321
- private static Exception Unwrap(Exception ex) =>
322
- ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
323
-
324
- private static unsafe void WriteResponse(byte[] json, byte** responsePtr, int* responseLenPtr)
234
+ [UnmanagedCallersOnly(EntryPoint = "Free",
235
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
236
+ public static unsafe void Free(byte* ptr)
325
237
  {
326
- var ptr = (byte*)Marshal.AllocHGlobal(json.Length + 1);
327
- Marshal.Copy(json, 0, (IntPtr)ptr, json.Length);
328
- ptr[json.Length] = 0;
329
- *responsePtr = ptr;
330
- *responseLenPtr = json.Length;
238
+ if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
331
239
  }
332
240
  }
333
-
334
- // ── JSON types ────────────────────────────────────────────────────────────────
335
-
336
- internal sealed record InvokeRequest(
337
- string? Assembly,
338
- string? TypeName,
339
- string? Method,
340
- int? Handle,
341
- List<JsonElement>? Args
342
- );
343
-
344
- internal sealed record InvokeResult(JsonElement? Result, string? Error);
345
-
346
- internal static class JsonOptions
347
- {
348
- internal static readonly JsonSerializerOptions Default = new()
349
- {
350
- PropertyNameCaseInsensitive = true,
351
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
352
- };
353
- }