diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
new file mode 100644
index 0000000..38db84a
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/JsCore.cs
@@ -0,0 +1,171 @@
+// JsCore.cs
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
+// https://github.com/gnh1201/welsonjs
+//
+using System;
+using System.Runtime.InteropServices;
+
+namespace WelsonJS.Launcher
+{
+ public sealed class JsCore : IDisposable
+ {
+ private IntPtr _runtime = IntPtr.Zero;
+ private IntPtr _context = IntPtr.Zero;
+ private bool _disposed;
+
+ public JsCore()
+ {
+ Check(JsCreateRuntime(0, IntPtr.Zero, out _runtime), nameof(JsCreateRuntime));
+ Check(JsCreateContext(_runtime, out _context), nameof(JsCreateContext));
+ Check(JsSetCurrentContext(_context), nameof(JsSetCurrentContext));
+ }
+
+ ///
+ /// Evaluates JavaScript and returns the result converted to string (via JsConvertValueToString).
+ ///
+ public string EvaluateToString(string script, string sourceUrl = "repl")
+ {
+ if (_disposed) throw new ObjectDisposedException(nameof(JsCore));
+ if (script is null) throw new ArgumentNullException(nameof(script));
+
+ Check(JsRunScript(script, IntPtr.Zero, sourceUrl, out var result), nameof(JsRunScript));
+
+ // Convert result -> JsString
+ Check(JsConvertValueToString(result, out var jsString), nameof(JsConvertValueToString));
+
+ // Extract pointer/length (UTF-16) and marshal to managed string
+ Check(JsStringToPointer(jsString, out var p, out var len), nameof(JsStringToPointer));
+ return Marshal.PtrToStringUni(p, checked((int)len));
+ }
+
+ ///
+ /// Evaluates JavaScript for side effects; discards the result.
+ ///
+ public void Execute(string script, string sourceUrl = "repl")
+ {
+ _ = EvaluateToString(script, sourceUrl);
+ }
+
+ private static void Check(JsErrorCode code, string op)
+ {
+ if (code != JsErrorCode.JsNoError)
+ throw new InvalidOperationException($"{op} failed with {code} (0x{(int)code:X}).");
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ try
+ {
+ // Unset the current context from the SAME physical thread that set it.
+ JsSetCurrentContext(IntPtr.Zero);
+ }
+ catch
+ {
+ // Swallow to ensure runtime is disposed.
+ }
+ finally
+ {
+ if (_runtime != IntPtr.Zero)
+ {
+ JsDisposeRuntime(_runtime);
+ _runtime = IntPtr.Zero;
+ }
+ _context = IntPtr.Zero;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ ~JsCore() => Dispose();
+
+ // =========================
+ // P/Invoke surface (as given)
+ // =========================
+
+ // Essential (expanded) JsErrorCode set matching ChakraCore’s headers layout.
+ // Values are grouped by category bases (0x10000, 0x20000, ...).
+ public enum JsErrorCode
+ {
+ // Success
+ JsNoError = 0,
+
+ // Category bases (useful when inspecting ranges)
+ JsErrorCategoryUsage = 0x10000,
+ JsErrorCategoryEngine = 0x20000,
+ JsErrorCategoryScript = 0x30000,
+ JsErrorCategoryFatal = 0x40000,
+
+ // Usage errors (0x10001+)
+ JsErrorInvalidArgument = 0x10001,
+ JsErrorNullArgument = 0x10002,
+ JsErrorNoCurrentContext = 0x10003,
+ JsErrorInExceptionState = 0x10004,
+ JsErrorNotImplemented = 0x10005,
+ JsErrorWrongThread = 0x10006,
+ JsErrorRuntimeInUse = 0x10007,
+ JsErrorBadSerializedScript = 0x10008,
+ JsErrorInDisabledState = 0x10009,
+ JsErrorCannotDisableExecution = 0x1000A,
+ JsErrorHeapEnumInProgress = 0x1000B,
+ JsErrorArgumentNotObject = 0x1000C,
+ JsErrorInProfileCallback = 0x1000D,
+ JsErrorInThreadServiceCallback = 0x1000E,
+ JsErrorCannotSerializeDebugScript = 0x1000F,
+ JsErrorAlreadyDebuggingContext = 0x10010,
+ JsErrorAlreadyProfilingContext = 0x10011,
+ JsErrorIdleNotEnabled = 0x10012,
+
+ // Engine errors (0x20001+)
+ JsErrorOutOfMemory = 0x20001,
+ JsErrorBadFPUState = 0x20002,
+
+ // Script errors (0x30001+)
+ JsErrorScriptException = 0x30001,
+ JsErrorScriptCompile = 0x30002,
+ JsErrorScriptTerminated = 0x30003,
+ JsErrorScriptEvalDisabled = 0x30004,
+
+ // Fatal (0x40001)
+ JsErrorFatal = 0x40001,
+
+ // Misc/diagnostic (0x50000+)
+ JsErrorWrongRuntime = 0x50000,
+ JsErrorDiagAlreadyInDebugMode = 0x50001,
+ JsErrorDiagNotInDebugMode = 0x50002,
+ JsErrorDiagNotAtBreak = 0x50003,
+ JsErrorDiagInvalidHandle = 0x50004,
+ JsErrorDiagObjectNotFound = 0x50005,
+ JsErrorDiagUnableToPerformAction = 0x50006,
+ }
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
+ public static extern JsErrorCode JsCreateRuntime(uint attributes, IntPtr callback, out IntPtr runtime);
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
+ public static extern JsErrorCode JsCreateContext(IntPtr runtime, out IntPtr context);
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
+ public static extern JsErrorCode JsSetCurrentContext(IntPtr context);
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
+ public static extern JsErrorCode JsRunScript(string script, IntPtr sourceContext, string sourceUrl, out IntPtr result);
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
+ public static extern JsErrorCode JsConvertValueToString(IntPtr value, out IntPtr stringValue);
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
+ public static extern JsErrorCode JsStringToPointer(IntPtr value, out IntPtr buffer, out UIntPtr length);
+
+ // Note: Unsetting is typically done via JsSetCurrentContext(IntPtr.Zero)
+ // Kept here only if your build exposes this symbol.
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "JsSetCurrentContext")]
+ public static extern JsErrorCode JsUnSetCurrentContext(IntPtr zero);
+
+ [DllImport("ChakraCore.dll", CallingConvention = CallingConvention.Cdecl)]
+ public static extern JsErrorCode JsDisposeRuntime(IntPtr runtime);
+ }
+}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs
new file mode 100644
index 0000000..b82b1a5
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/JsSerializer.cs
@@ -0,0 +1,293 @@
+// JsSerializer.cs
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
+// https://github.com/gnh1201/welsonjs
+//
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace WelsonJS.Launcher
+{
+ public sealed class JsSerializer : IDisposable
+ {
+ private readonly JsCore _core;
+ private readonly bool _ownsCore;
+
+ public JsSerializer() : this(new JsCore(), true) { }
+
+ public JsSerializer(JsCore core, bool ownsCore)
+ {
+ if (core == null) throw new ArgumentNullException("core");
+ _core = core;
+ _ownsCore = ownsCore;
+ }
+
+ public bool IsValid(string json)
+ {
+ if (json == null) throw new ArgumentNullException("json");
+ string script =
+ "(function(){try{JSON.parse(" + Q(json) + ");return '1';}catch(_){return '0';}})()";
+ string r = _core.EvaluateToString(script);
+ return r == "1";
+ }
+
+ public string Minify(string json)
+ {
+ if (json == null) throw new ArgumentNullException("json");
+ string script = "JSON.stringify(JSON.parse(" + Q(json) + "))";
+ return _core.EvaluateToString(script);
+ }
+
+ public string Pretty(string json, int space)
+ {
+ if (json == null) throw new ArgumentNullException("json");
+ space = Clamp(space, 0, 10);
+ string script = "JSON.stringify(JSON.parse(" + Q(json) + "),null," + space.ToString(CultureInfo.InvariantCulture) + ")";
+ return _core.EvaluateToString(script);
+ }
+
+ public string Normalize(string json)
+ {
+ return Minify(json);
+ }
+
+ ///
+ /// Extracts a value by a simple path of property names (numeric segment as string = array index).
+ /// Returns the selected value as a JSON string.
+ ///
+ public string Extract(string json, params string[] path)
+ {
+ if (path == null) path = new string[0];
+ object[] mixed = new object[path.Length];
+ for (int i = 0; i < path.Length; i++) mixed[i] = path[i];
+ return Extract(json, mixed);
+ }
+
+ ///
+ /// Extracts by a mixed path. Segments can be strings (object keys) or integers (array indices).
+ /// Returns the selected value as a JSON string (e.g., a JS string returns with quotes).
+ /// Usage: Extract(json, "items", 0, "name")
+ ///
+ public string Extract(string json, params object[] path)
+ {
+ if (json == null) throw new ArgumentNullException("json");
+ if (path == null) path = new object[0];
+
+ string jsPath = BuildJsPath(path);
+
+ var sb = new StringBuilder();
+ sb.Append("(function(){var v=JSON.parse(").Append(Q(json)).Append(");");
+ sb.Append("var p=").Append(jsPath).Append(";");
+ sb.Append("for(var i=0;i
(ReferenceEqualityComparer.Instance), 0);
+ string script = "JSON.stringify((" + expr + "),null," + space.ToString(CultureInfo.InvariantCulture) + ")";
+ return _core.EvaluateToString(script);
+ }
+
+ private static int Clamp(int v, int min, int max)
+ {
+ if (v < min) return min;
+ if (v > max) return max;
+ return v;
+ }
+
+ ///
+ /// Encode a .NET string as a JS double-quoted string literal.
+ ///
+ private static string Q(string s)
+ {
+ if (s == null) return "null";
+ var sb = new StringBuilder(s.Length + 16);
+ sb.Append('"');
+ for (int i = 0; i < s.Length; i++)
+ {
+ char ch = s[i];
+ switch (ch)
+ {
+ case '\\': sb.Append(@"\\"); break;
+ case '"': sb.Append("\\\""); break;
+ case '\b': sb.Append(@"\b"); break;
+ case '\f': sb.Append(@"\f"); break;
+ case '\n': sb.Append(@"\n"); break;
+ case '\r': sb.Append(@"\r"); break;
+ case '\t': sb.Append(@"\t"); break;
+ default:
+ if (char.IsControl(ch))
+ {
+ sb.Append("\\u");
+ sb.Append(((int)ch).ToString("X4"));
+ }
+ else
+ {
+ sb.Append(ch);
+ }
+ break;
+ }
+ }
+ sb.Append('"');
+ return sb.ToString();
+ }
+
+ ///
+ /// Builds a JS array literal representing the path.
+ /// Numeric segments are emitted as numbers; others as strings.
+ ///
+ private static string BuildJsPath(object[] segments)
+ {
+ if (segments == null || segments.Length == 0) return "[]";
+
+ var sb = new StringBuilder();
+ sb.Append('[');
+ for (int i = 0; i < segments.Length; i++)
+ {
+ if (i > 0) sb.Append(',');
+
+ object seg = segments[i];
+
+ // Treat integral types as numbers for array indexing
+ if (seg is sbyte || seg is byte ||
+ seg is short || seg is ushort ||
+ seg is int || seg is uint ||
+ seg is long || seg is ulong)
+ {
+ sb.Append(Convert.ToString(seg, CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ string str = (seg == null) ? string.Empty : Convert.ToString(seg, CultureInfo.InvariantCulture);
+ sb.Append(Q(str));
+ }
+ }
+ sb.Append(']');
+ return sb.ToString();
+ }
+
+ private static bool IsNumeric(object v)
+ {
+ if (v == null) return false;
+ Type t = v.GetType();
+ t = Nullable.GetUnderlyingType(t) ?? t;
+ return t == typeof(byte) || t == typeof(sbyte) ||
+ t == typeof(short) || t == typeof(ushort) ||
+ t == typeof(int) || t == typeof(uint) ||
+ t == typeof(long) || t == typeof(ulong) ||
+ t == typeof(float) || t == typeof(double) ||
+ t == typeof(decimal);
+ }
+
+ private static bool IsImmutableLike(object v)
+ {
+ return v is string || v is bool ||
+ v is byte || v is sbyte ||
+ v is short || v is ushort ||
+ v is int || v is uint ||
+ v is long || v is ulong ||
+ v is float || v is double || v is decimal ||
+ v is Guid || v is DateTime || v is DateTimeOffset;
+ }
+
+ ///
+ /// Builds a safe JS expression for a .NET value (no engine calls here).
+ /// Engine will stringify the produced expression via JSON.stringify.
+ ///
+ private static string BuildJsExpression(object value, HashSet