@testsmith/api-spector 0.4.9 → 0.5.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/out/main/index.js CHANGED
@@ -22,12 +22,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  const electron = require("electron");
25
- const handle = require("./chunks/handle-CF2LjTGV.js");
25
+ const handle = require("./chunks/handle-b6bp3fb0.js");
26
26
  const path = require("path");
27
- const fs = require("fs");
27
+ const fs$1 = require("fs");
28
28
  const promises = require("fs/promises");
29
29
  const crypto = require("crypto");
30
- const requestExec = require("./chunks/request-exec-DeXgxps8.js");
30
+ const requestExec = require("./chunks/request-exec-D_xyyt3_.js");
31
31
  const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
32
32
  const uuid = require("uuid");
33
33
  const jsYaml = require("js-yaml");
@@ -37,15 +37,18 @@ const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
37
37
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
38
38
  const http = require("http");
39
39
  const WebSocket = require("ws");
40
- const soapHandler = require("./chunks/soap-handler-pOrJ625E.js");
41
- const os = require("os");
42
- const snapshots = require("./chunks/snapshots-8wLrfQcq.js");
40
+ const fs = require("node:fs");
41
+ const os = require("node:os");
42
+ const path$1 = require("node:path");
43
+ const grpc = require("@grpc/grpc-js");
44
+ const protoLoader = require("@grpc/proto-loader");
45
+ const soapHandler = require("./chunks/soap-handler-BYkE6MyE.js");
46
+ const promises$1 = require("node:fs/promises");
47
+ const os$1 = require("os");
48
+ const snapshots = require("./chunks/snapshots-BJiZNuRN.js");
43
49
  const simpleGit = require("simple-git");
44
50
  const recorder = require("./chunks/recorder-0Ij921El.js");
45
51
  require("tls");
46
- require("node:fs");
47
- require("node:os");
48
- require("node:path");
49
52
  require("node:crypto");
50
53
  require("dayjs");
51
54
  require("vm");
@@ -54,6 +57,27 @@ require("jsonpath-plus");
54
57
  require("@xmldom/xmldom");
55
58
  require("ajv");
56
59
  require("https");
60
+ function _interopNamespaceDefault(e) {
61
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
62
+ if (e) {
63
+ for (const k in e) {
64
+ if (k !== "default") {
65
+ const d = Object.getOwnPropertyDescriptor(e, k);
66
+ Object.defineProperty(n, k, d.get ? d : {
67
+ enumerable: true,
68
+ get: () => e[k]
69
+ });
70
+ }
71
+ }
72
+ }
73
+ n.default = e;
74
+ return Object.freeze(n);
75
+ }
76
+ const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
77
+ const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
78
+ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path$1);
79
+ const grpc__namespace = /* @__PURE__ */ _interopNamespaceDefault(grpc);
80
+ const protoLoader__namespace = /* @__PURE__ */ _interopNamespaceDefault(protoLoader);
57
81
  const LAST_WS_FILE = path.join(electron.app.getPath("userData"), "last-workspace.json");
58
82
  const RECENTS_FILE = path.join(electron.app.getPath("userData"), "recent-workspaces.json");
59
83
  const MAX_RECENTS = 10;
@@ -179,29 +203,29 @@ function readmeContents(workspaceFileName) {
179
203
  ].join("\n");
180
204
  }
181
205
  async function ensureReadme(workspaceDir2, workspaceFileName) {
182
- const path$1 = path.join(workspaceDir2, "README.md");
206
+ const path$12 = path.join(workspaceDir2, "README.md");
183
207
  try {
184
- await promises.readFile(path$1, "utf8");
208
+ await promises.readFile(path$12, "utf8");
185
209
  return;
186
210
  } catch (err) {
187
211
  if (err.code !== "ENOENT") {
188
212
  console.warn("file-handler: could not check README.md", err);
189
213
  return;
190
214
  }
191
- await atomicWrite(path$1, readmeContents(workspaceFileName));
215
+ await atomicWrite(path$12, readmeContents(workspaceFileName));
192
216
  }
193
217
  }
194
218
  async function ensureGitignore(workspaceDir2) {
195
- const path$1 = path.join(workspaceDir2, ".gitignore");
219
+ const path$12 = path.join(workspaceDir2, ".gitignore");
196
220
  try {
197
- await promises.readFile(path$1, "utf8");
221
+ await promises.readFile(path$12, "utf8");
198
222
  return;
199
223
  } catch (err) {
200
224
  if (err.code !== "ENOENT") {
201
225
  console.warn("file-handler: could not check .gitignore", err);
202
226
  return;
203
227
  }
204
- await atomicWrite(path$1, SPECTOR_GITIGNORE);
228
+ await atomicWrite(path$12, SPECTOR_GITIGNORE);
205
229
  }
206
230
  }
207
231
  async function ensureVscodeFileAssociation(workspaceDir2) {
@@ -4775,6 +4799,152 @@ function registerWsHandlers(ipc) {
4775
4799
  }
4776
4800
  });
4777
4801
  }
4802
+ const calls = /* @__PURE__ */ new Map();
4803
+ function closeAllGrpcConnections() {
4804
+ for (const [, c] of calls) {
4805
+ try {
4806
+ c.cancel();
4807
+ } catch {
4808
+ }
4809
+ }
4810
+ calls.clear();
4811
+ }
4812
+ const LOADER_OPTS = {
4813
+ keepCase: true,
4814
+ longs: String,
4815
+ enums: String,
4816
+ defaults: true,
4817
+ oneofs: true
4818
+ };
4819
+ function resolveProtoFile(src) {
4820
+ const importPaths = (src.importPaths ?? []).filter(Boolean);
4821
+ if (src.protoPath) {
4822
+ return { file: src.protoPath, includeDirs: [path__namespace.dirname(src.protoPath), ...importPaths] };
4823
+ }
4824
+ if (src.protoSource && src.protoSource.trim()) {
4825
+ const dir = fs__namespace.mkdtempSync(path__namespace.join(os__namespace.tmpdir(), "apispector-proto-"));
4826
+ const file = path__namespace.join(dir, "main.proto");
4827
+ fs__namespace.writeFileSync(file, src.protoSource, "utf8");
4828
+ return { file, includeDirs: [dir, ...importPaths] };
4829
+ }
4830
+ throw new Error("No proto source: paste a .proto or provide a file path.");
4831
+ }
4832
+ function loadPackage(src) {
4833
+ const { file, includeDirs } = resolveProtoFile(src);
4834
+ return protoLoader__namespace.loadSync(file, { ...LOADER_OPTS, includeDirs });
4835
+ }
4836
+ function enumerateServices(pkgDef) {
4837
+ const services = [];
4838
+ for (const [name, def] of Object.entries(pkgDef)) {
4839
+ const methods = Object.entries(def).filter(([, m]) => m && typeof m === "object" && "requestStream" in m).map(([mName, m]) => {
4840
+ const md = m;
4841
+ return { name: mName, requestStream: !!md.requestStream, responseStream: !!md.responseStream };
4842
+ });
4843
+ if (methods.length) services.push({ name, methods });
4844
+ }
4845
+ return services;
4846
+ }
4847
+ function resolveService(grpcObj, serviceName) {
4848
+ let node = grpcObj;
4849
+ for (const part of serviceName.split(".")) {
4850
+ node = node?.[part];
4851
+ if (!node) throw new Error(`Service "${serviceName}" not found in the proto.`);
4852
+ }
4853
+ if (typeof node !== "function") throw new Error(`"${serviceName}" is not a service.`);
4854
+ return node;
4855
+ }
4856
+ function normalizeTarget(url) {
4857
+ return url.replace(/^[a-z0-9+.-]+:\/\//i, "").replace(/\/+$/, "");
4858
+ }
4859
+ function registerGrpcHandlers(ipc) {
4860
+ handle.handleIpc(ipc, handle.IPC.grpc.loadProto, async (_e, src) => {
4861
+ return { services: enumerateServices(loadPackage(src)) };
4862
+ });
4863
+ handle.handleIpc(ipc, handle.IPC.grpc.invoke, async (event, requestId, opts) => {
4864
+ calls.get(requestId)?.cancel();
4865
+ calls.delete(requestId);
4866
+ const sendStatus = (status, extra = {}) => event.sender.send(handle.IPC.grpc.status, { requestId, status, ...extra });
4867
+ const sendMessage = (data) => {
4868
+ const message = {
4869
+ id: uuid.v4(),
4870
+ direction: "received",
4871
+ data: typeof data === "string" ? data : JSON.stringify(data, null, 2),
4872
+ timestamp: Date.now()
4873
+ };
4874
+ event.sender.send(handle.IPC.grpc.message, { requestId, message });
4875
+ };
4876
+ let pkgDef;
4877
+ let ServiceCtor;
4878
+ let request;
4879
+ try {
4880
+ pkgDef = loadPackage(opts);
4881
+ const grpcObj = grpc__namespace.loadPackageDefinition(pkgDef);
4882
+ ServiceCtor = resolveService(grpcObj, opts.serviceName);
4883
+ request = opts.message.trim() ? JSON.parse(opts.message) : {};
4884
+ } catch (err) {
4885
+ sendStatus("error", { error: err instanceof Error ? err.message : String(err) });
4886
+ return;
4887
+ }
4888
+ const serviceDef = ServiceCtor.service;
4889
+ const methodKey = Object.keys(serviceDef).find((k) => k === opts.methodName) ?? Object.keys(serviceDef).find((k) => k.toLowerCase() === opts.methodName.toLowerCase());
4890
+ if (!methodKey) {
4891
+ sendStatus("error", { error: `Method "${opts.methodName}" not found on ${opts.serviceName}.` });
4892
+ return;
4893
+ }
4894
+ const def = serviceDef[methodKey];
4895
+ if (def.requestStream) {
4896
+ sendStatus("error", { error: "Client-streaming methods are not supported yet." });
4897
+ return;
4898
+ }
4899
+ const creds = opts.plaintext ? grpc__namespace.credentials.createInsecure() : grpc__namespace.credentials.createSsl();
4900
+ const client = new ServiceCtor(normalizeTarget(opts.target), creds);
4901
+ const metadata = new grpc__namespace.Metadata();
4902
+ for (const [k, v] of Object.entries(opts.metadata ?? {})) metadata.set(k, v);
4903
+ const statusName = (code) => Object.keys(grpc__namespace.status).find((k) => grpc__namespace.status[k] === code) ?? String(code);
4904
+ const cleanup = () => {
4905
+ calls.delete(requestId);
4906
+ try {
4907
+ client.close();
4908
+ } catch {
4909
+ }
4910
+ };
4911
+ sendStatus("running");
4912
+ try {
4913
+ const method = client[methodKey].bind(client);
4914
+ if (def.responseStream) {
4915
+ const call = method(request, metadata);
4916
+ calls.set(requestId, { cancel: () => call.cancel() });
4917
+ call.on("data", (d) => sendMessage(d));
4918
+ call.on("error", (e) => {
4919
+ sendStatus("error", { code: e.code, codeName: e.code !== void 0 ? statusName(e.code) : void 0, error: e.details || e.message });
4920
+ cleanup();
4921
+ });
4922
+ call.on("status", (s) => {
4923
+ if (s.code === grpc__namespace.status.OK) sendStatus("completed", { code: s.code, codeName: statusName(s.code) });
4924
+ cleanup();
4925
+ });
4926
+ } else {
4927
+ const call = method(request, metadata, (err, response) => {
4928
+ if (err) {
4929
+ sendStatus("error", { code: err.code, codeName: err.code !== void 0 ? statusName(err.code) : void 0, error: err.details || err.message });
4930
+ } else {
4931
+ sendMessage(response);
4932
+ sendStatus("completed", { code: grpc__namespace.status.OK, codeName: "OK" });
4933
+ }
4934
+ cleanup();
4935
+ });
4936
+ calls.set(requestId, { cancel: () => call.cancel() });
4937
+ }
4938
+ } catch (err) {
4939
+ sendStatus("error", { error: err instanceof Error ? err.message : String(err) });
4940
+ cleanup();
4941
+ }
4942
+ });
4943
+ handle.handleIpc(ipc, handle.IPC.grpc.cancel, async (_e, requestId) => {
4944
+ calls.get(requestId)?.cancel();
4945
+ calls.delete(requestId);
4946
+ });
4947
+ }
4778
4948
  function isJsonContentType(ct) {
4779
4949
  return !!ct && /\bjson\b/i.test(ct);
4780
4950
  }
@@ -5065,6 +5235,26 @@ function registerDocsHandlers(ipc) {
5065
5235
  return generateMarkdown(payload);
5066
5236
  });
5067
5237
  }
5238
+ function registerCoverageHandlers(ipc) {
5239
+ handle.handleIpc(ipc, handle.IPC.coverage.loadSpec, async (_e, source) => {
5240
+ if (source.text && source.text.trim()) {
5241
+ const t = source.text.trim();
5242
+ return t.startsWith("{") ? JSON.parse(t) : jsYaml.load(t);
5243
+ }
5244
+ if (source.url) {
5245
+ const resp = await undici.fetch(source.url);
5246
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} fetching ${source.url}`);
5247
+ const raw = await resp.text();
5248
+ const ct = resp.headers.get("content-type") ?? "";
5249
+ return ct.includes("yaml") || /\.ya?ml$/i.test(source.url) ? jsYaml.load(raw) : JSON.parse(raw);
5250
+ }
5251
+ if (source.path) {
5252
+ const raw = await promises$1.readFile(source.path, "utf8");
5253
+ return /\.ya?ml$/i.test(source.path) ? jsYaml.load(raw) : JSON.parse(raw);
5254
+ }
5255
+ throw new Error("No spec source: provide a file path, URL, or pasted text.");
5256
+ });
5257
+ }
5068
5258
  function inferSchema(data) {
5069
5259
  if (data === null || data === void 0) return { type: "null" };
5070
5260
  if (typeof data === "boolean") return { type: "boolean" };
@@ -5101,7 +5291,7 @@ async function resolveSnapshotSpec(relPath) {
5101
5291
  const dir = getWorkspaceDir();
5102
5292
  if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
5103
5293
  const snap = await snapshots.loadSnapshot(dir, relPath);
5104
- const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
5294
+ const tmp = path.join(os$1.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
5105
5295
  await promises.writeFile(tmp, snap.spec, "utf8");
5106
5296
  return { specPath: tmp };
5107
5297
  }
@@ -5602,7 +5792,7 @@ function isNewer(a, b) {
5602
5792
  return a2 > b2;
5603
5793
  }
5604
5794
  async function checkForUpdate() {
5605
- const current = "0.4.9";
5795
+ const current = "0.5.1";
5606
5796
  try {
5607
5797
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
5608
5798
  if (!res.ok) return null;
@@ -5647,15 +5837,15 @@ function loadAppIcon() {
5647
5837
  path.join(__dirname, "../../build/icon.png")
5648
5838
  ];
5649
5839
  for (const p of pngCandidates) {
5650
- if (fs.existsSync(p)) return electron.nativeImage.createFromPath(p);
5840
+ if (fs$1.existsSync(p)) return electron.nativeImage.createFromPath(p);
5651
5841
  }
5652
5842
  const svgCandidates = [
5653
5843
  path.join(electron.app.getAppPath(), "resources/icon.svg"),
5654
5844
  path.join(__dirname, "../../resources/icon.svg")
5655
5845
  ];
5656
5846
  for (const p of svgCandidates) {
5657
- if (fs.existsSync(p)) {
5658
- const dataUrl = "data:image/svg+xml;base64," + fs.readFileSync(p).toString("base64");
5847
+ if (fs$1.existsSync(p)) {
5848
+ const dataUrl = "data:image/svg+xml;base64," + fs$1.readFileSync(p).toString("base64");
5659
5849
  return electron.nativeImage.createFromDataURL(dataUrl);
5660
5850
  }
5661
5851
  }
@@ -5718,8 +5908,10 @@ electron.app.whenReady().then(async () => {
5718
5908
  registerOAuth2Handlers(electron.ipcMain);
5719
5909
  registerVaultHandlers(electron.ipcMain);
5720
5910
  registerWsHandlers(electron.ipcMain);
5911
+ registerGrpcHandlers(electron.ipcMain);
5721
5912
  soapHandler.registerSoapHandlers(electron.ipcMain);
5722
5913
  registerDocsHandlers(electron.ipcMain);
5914
+ registerCoverageHandlers(electron.ipcMain);
5723
5915
  registerContractHandlers(electron.ipcMain);
5724
5916
  registerGitHandlers(electron.ipcMain);
5725
5917
  registerRecordHandlers(electron.ipcMain, () => electron.BrowserWindow.getAllWindows()[0]?.webContents ?? null);
@@ -5736,5 +5928,6 @@ electron.app.on("window-all-closed", () => {
5736
5928
  });
5737
5929
  electron.app.on("before-quit", async () => {
5738
5930
  closeAllWsConnections();
5931
+ closeAllGrpcConnections();
5739
5932
  await mockServer.stopAll();
5740
5933
  });
package/out/main/lib.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
4
- const requestExec = require("./chunks/request-exec-DeXgxps8.js");
4
+ const requestExec = require("./chunks/request-exec-D_xyyt3_.js");
5
5
  require("http");
6
6
  require("crypto");
7
7
  require("vm");
@@ -10,7 +10,7 @@ require("@xmldom/xmldom");
10
10
  require("undici");
11
11
  require("fs/promises");
12
12
  require("tls");
13
- require("./chunks/handle-CF2LjTGV.js");
13
+ require("./chunks/handle-b6bp3fb0.js");
14
14
  require("node:fs");
15
15
  require("node:os");
16
16
  require("node:path");
@@ -2,13 +2,13 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const requestExec = require("./chunks/request-exec-DeXgxps8.js");
5
+ const requestExec = require("./chunks/request-exec-D_xyyt3_.js");
6
6
  const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
7
7
  const environments = require("./chunks/environments-iM3SUM-4.js");
8
8
  const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
9
9
  require("undici");
10
10
  require("tls");
11
- require("./chunks/handle-CF2LjTGV.js");
11
+ require("./chunks/handle-b6bp3fb0.js");
12
12
  require("node:fs");
13
13
  require("node:os");
14
14
  require("node:path");
@@ -402,7 +402,7 @@ async function main() {
402
402
  } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
403
403
  console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
404
404
  }
405
- const version = `v${"0.4.9"}`;
405
+ const version = `v${"0.5.1"}`;
406
406
  console.log("");
407
407
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
408
408
  console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
package/out/main/wsdl.js CHANGED
@@ -4,10 +4,10 @@ const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const https = require("https");
6
6
  const http = require("http");
7
- const soapHandler = require("./chunks/soap-handler-pOrJ625E.js");
8
- const _import = require("./chunks/import-ChCkdHOB.js");
7
+ const soapHandler = require("./chunks/soap-handler-BYkE6MyE.js");
8
+ const _import = require("./chunks/import-BeY7gjWL.js");
9
9
  const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
10
- require("./chunks/handle-CF2LjTGV.js");
10
+ require("./chunks/handle-b6bp3fb0.js");
11
11
  require("@xmldom/xmldom");
12
12
  require("uuid");
13
13
  require("crypto");
@@ -105,10 +105,28 @@ const IPC = {
105
105
  fetch: "wsdl:fetch",
106
106
  import: "wsdl:import"
107
107
  },
108
+ // ─── gRPC ──────────────────────────────────────────────────────────────────
109
+ grpc: {
110
+ /** Load a .proto (source or path) and enumerate its services/methods. */
111
+ loadProto: "grpc:loadProto",
112
+ /** Invoke a method (unary or server-streaming). Streams back messages. */
113
+ invoke: "grpc:invoke",
114
+ /** Cancel an in-flight call. */
115
+ cancel: "grpc:cancel",
116
+ /** Event: a received message frame. */
117
+ message: "grpc:message",
118
+ /** Event: call status change (running / completed / error, with code). */
119
+ status: "grpc:status"
120
+ },
108
121
  // ─── Docs generation ───────────────────────────────────────────────────────
109
122
  docs: {
110
123
  generate: "docs:generate"
111
124
  },
125
+ // ─── OpenAPI coverage ────────────────────────────────────────────────────────
126
+ coverage: {
127
+ /** Read + parse an OpenAPI spec (file path, URL, or raw text). */
128
+ loadSpec: "coverage:loadSpec"
129
+ },
112
130
  // ─── Contract testing ──────────────────────────────────────────────────────
113
131
  contract: {
114
132
  run: "contract:run",
@@ -288,6 +306,20 @@ const api = {
288
306
  electron.ipcRenderer.removeAllListeners(IPC.ws.message);
289
307
  electron.ipcRenderer.removeAllListeners(IPC.ws.status);
290
308
  },
309
+ // ─── gRPC ─────────────────────────────────────────────────────────────────
310
+ grpcLoadProto: (src) => electron.ipcRenderer.invoke(IPC.grpc.loadProto, src),
311
+ grpcInvoke: (requestId, opts) => electron.ipcRenderer.invoke(IPC.grpc.invoke, requestId, opts),
312
+ grpcCancel: (requestId) => electron.ipcRenderer.invoke(IPC.grpc.cancel, requestId),
313
+ onGrpcMessage: (cb) => {
314
+ electron.ipcRenderer.on(IPC.grpc.message, (_e, payload) => cb(payload));
315
+ },
316
+ onGrpcStatus: (cb) => {
317
+ electron.ipcRenderer.on(IPC.grpc.status, (_e, payload) => cb(payload));
318
+ },
319
+ offGrpcEvents: () => {
320
+ electron.ipcRenderer.removeAllListeners(IPC.grpc.message);
321
+ electron.ipcRenderer.removeAllListeners(IPC.grpc.status);
322
+ },
291
323
  // ─── Streamed responses (SSE / NDJSON / chunked) ──────────────────────────
292
324
  onRequestStreamEvent: (cb) => {
293
325
  electron.ipcRenderer.on(IPC.request.streamEvent, (_e, payload) => cb(payload));
@@ -304,6 +336,8 @@ const api = {
304
336
  wsdlImport: (opts) => electron.ipcRenderer.invoke(IPC.wsdl.import, opts),
305
337
  // ─── Docs generation ──────────────────────────────────────────────────────
306
338
  generateDocs: (payload) => electron.ipcRenderer.invoke(IPC.docs.generate, payload),
339
+ // ─── OpenAPI coverage ─────────────────────────────────────────────────────
340
+ coverageLoadSpec: (source) => electron.ipcRenderer.invoke(IPC.coverage.loadSpec, source),
307
341
  // ─── Contract testing ─────────────────────────────────────────────────────
308
342
  runContracts: (payload) => electron.ipcRenderer.invoke(IPC.contract.run, payload),
309
343
  inferContractSchema: (jsonBody) => electron.ipcRenderer.invoke(IPC.contract.inferSchema, jsonBody),