@jxsuite/server 0.23.2 → 0.25.0

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.
@@ -11,10 +11,11 @@ import { resolve, relative, basename, dirname, isAbsolute } from "node:path";
11
11
  import { readdir, stat, readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
12
12
  import { readFileSync, existsSync } from "node:fs";
13
13
  import { transpileJxMarkdown } from "@jxsuite/parser/transpile";
14
- import * as claude from "./claude-session.js";
14
+ import * as claude from "./claude-session.ts";
15
+ import type { ClassJsonDef } from "./types.ts";
15
16
 
16
17
  /** Normalise a path to forward slashes (Windows `path` module returns backslashes). */
17
- const fwd = (/** @type {string} */ p) => p.replaceAll("\\", "/");
18
+ const fwd = (p: string) => p.replaceAll("\\", "/");
18
19
 
19
20
  /**
20
21
  * Check that a path is under either the server root OR the active project root. This allows file
@@ -24,7 +25,7 @@ const fwd = (/** @type {string} */ p) => p.replaceAll("\\", "/");
24
25
  * @param {string} root
25
26
  * @param {string | null} activeProjectRoot
26
27
  */
27
- function assertAccessible(filePath, root, activeProjectRoot) {
28
+ function assertAccessible(filePath: string, root: string, activeProjectRoot: string | null) {
28
29
  const rel = relative(root, filePath);
29
30
  if (!rel.startsWith("..") && !rel.startsWith("/")) return;
30
31
  if (activeProjectRoot) {
@@ -34,8 +35,15 @@ function assertAccessible(filePath, root, activeProjectRoot) {
34
35
  throw new Error("Path outside project root");
35
36
  }
36
37
 
37
- /** @type {Record<string, string>} */
38
- const statusMap = { M: "M", T: "T", A: "A", D: "D", R: "R", C: "C", U: "U" };
38
+ const statusMap: Record<string, string> = {
39
+ M: "M",
40
+ T: "T",
41
+ A: "A",
42
+ D: "D",
43
+ R: "R",
44
+ C: "C",
45
+ U: "U",
46
+ };
39
47
 
40
48
  /**
41
49
  * Parse raw `git status --porcelain=v2 --branch` output into structured data.
@@ -50,7 +58,7 @@ const statusMap = { M: "M", T: "T", A: "A", D: "D", R: "R", C: "C", U: "U" };
50
58
  * remotes?: string[];
51
59
  * }}
52
60
  */
53
- export function parseGitStatus(out) {
61
+ export function parseGitStatus(out: string) {
54
62
  let branch = "";
55
63
  let ahead = 0;
56
64
  let behind = 0;
@@ -108,7 +116,12 @@ export function parseGitStatus(out) {
108
116
  * @param {string | null} [activeProjectRoot]
109
117
  * @returns {Promise<Response | null>}
110
118
  */
111
- export async function handleStudioApi(req, url, root, activeProjectRoot = null) {
119
+ export async function handleStudioApi(
120
+ req: Request,
121
+ url: URL,
122
+ root: string,
123
+ activeProjectRoot: string | null = null,
124
+ ) {
112
125
  const path = url.pathname;
113
126
 
114
127
  // Project metadata
@@ -131,11 +144,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
131
144
  const absDir = isAbsolute(dir) ? dir : resolve(root, dir);
132
145
  try {
133
146
  assertAccessible(absDir, root, activeProjectRoot);
134
- } catch (/** @type {unknown} */ e) {
135
- return Response.json(
136
- { error: /** @type {{ message?: string }} */ (e).message },
137
- { status: 400 },
138
- );
147
+ } catch (e) {
148
+ return Response.json({ error: (e as Error).message }, { status: 400 });
139
149
  }
140
150
  try {
141
151
  const projectRoot = fwd(absDir);
@@ -167,11 +177,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
167
177
  } catch {}
168
178
 
169
179
  return Response.json({ isSiteProject, projectConfig, directories, projectRoot });
170
- } catch (/** @type {unknown} */ e) {
171
- return Response.json(
172
- { error: /** @type {{ message?: string }} */ (e).message },
173
- { status: 500 },
174
- );
180
+ } catch (e) {
181
+ return Response.json({ error: (e as Error).message }, { status: 500 });
175
182
  }
176
183
  }
177
184
 
@@ -205,11 +212,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
205
212
  dir = parent;
206
213
  }
207
214
  return Response.json({ sitePath: null });
208
- } catch (/** @type {unknown} */ e) {
209
- return Response.json(
210
- { error: /** @type {{ message?: string }} */ (e).message },
211
- { status: 500 },
212
- );
215
+ } catch (e) {
216
+ return Response.json({ error: (e as Error).message }, { status: 500 });
213
217
  }
214
218
  }
215
219
 
@@ -230,11 +234,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
230
234
  }
231
235
  } catch {}
232
236
  return Response.json({ path: null });
233
- } catch (/** @type {unknown} */ e) {
234
- return Response.json(
235
- { error: /** @type {{ message?: string }} */ (e).message },
236
- { status: 500 },
237
- );
237
+ } catch (e) {
238
+ return Response.json({ error: (e as Error).message }, { status: 500 });
238
239
  }
239
240
  }
240
241
 
@@ -260,11 +261,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
260
261
  } catch {}
261
262
  }
262
263
  return Response.json(sites);
263
- } catch (/** @type {unknown} */ e) {
264
- return Response.json(
265
- { error: /** @type {{ message?: string }} */ (e).message },
266
- { status: 500 },
267
- );
264
+ } catch (e) {
265
+ return Response.json({ error: (e as Error).message }, { status: 500 });
268
266
  }
269
267
  }
270
268
 
@@ -285,11 +283,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
285
283
  const config = JSON.parse(await readFile(resolve(destPath, "project.json"), "utf8"));
286
284
  const projectRoot = fwd(relative(root, destPath));
287
285
  return Response.json({ root: projectRoot, config });
288
- } catch (/** @type {unknown} */ e) {
289
- return Response.json(
290
- { error: /** @type {{ message?: string }} */ (e).message },
291
- { status: 500 },
292
- );
286
+ } catch (e) {
287
+ return Response.json({ error: (e as Error).message }, { status: 500 });
293
288
  }
294
289
  }
295
290
 
@@ -300,15 +295,12 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
300
295
  const absDir = isAbsolute(dir) ? dir : resolve(root, dir);
301
296
  try {
302
297
  assertAccessible(absDir, root, activeProjectRoot);
303
- } catch (/** @type {unknown} */ e) {
304
- return Response.json(
305
- { error: /** @type {{ message?: string }} */ (e).message },
306
- { status: 400 },
307
- );
298
+ } catch (e) {
299
+ return Response.json({ error: (e as Error).message }, { status: 400 });
308
300
  }
309
301
 
310
302
  /** Report a path relative to the active project root (or server root as fallback). */
311
- const reportRelative = (/** @type {string} */ fp) => {
303
+ const reportRelative = (fp: string) => {
312
304
  if (activeProjectRoot) {
313
305
  const rel = relative(activeProjectRoot, fp);
314
306
  if (!rel.startsWith("..")) return fwd(rel);
@@ -352,11 +344,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
352
344
  });
353
345
  }
354
346
  return Response.json(files);
355
- } catch (/** @type {unknown} */ e) {
356
- return Response.json(
357
- { error: /** @type {{ message?: string }} */ (e).message },
358
- { status: 500 },
359
- );
347
+ } catch (e) {
348
+ return Response.json({ error: (e as Error).message }, { status: 500 });
360
349
  }
361
350
  }
362
351
 
@@ -366,11 +355,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
366
355
  const scanRoot = isAbsolute(dir) ? dir : resolve(root, dir);
367
356
  try {
368
357
  assertAccessible(scanRoot, root, activeProjectRoot);
369
- } catch (/** @type {unknown} */ e) {
370
- return Response.json(
371
- { error: /** @type {{ message?: string }} */ (e).message },
372
- { status: 400 },
373
- );
358
+ } catch (e) {
359
+ return Response.json({ error: (e as Error).message }, { status: 400 });
374
360
  }
375
361
  try {
376
362
  const glob = new Bun.Glob("**/*.{json,md}");
@@ -406,14 +392,16 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
406
392
  // Shorthand: "key": "value" or "key": 0 etc.
407
393
  if (typeof d !== "object") return true;
408
394
  // Full form: skip computed/handler/prototype entries
409
- return !d.$prototype && !d.$handler && !d.$compute;
395
+ const obj = d as Record<string, unknown>;
396
+ return !obj.$prototype && !obj.$handler && !obj.$compute;
410
397
  })
411
398
  .map(([name, d]) => {
412
- if (typeof d !== "object") {
399
+ if (typeof d !== "object" || d === null) {
413
400
  // Shorthand: infer type from value
414
401
  return { name, type: typeof d, default: d };
415
402
  }
416
- return { name, type: d.type, default: d.default, format: d.format };
403
+ const obj = d as Record<string, unknown>;
404
+ return { name, type: obj.type, default: obj.default, format: obj.format };
417
405
  }),
418
406
  hasElements: Array.isArray(content.$elements) && content.$elements.length > 0,
419
407
  });
@@ -464,16 +452,14 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
464
452
  source: "npm",
465
453
  package: name,
466
454
  description: decl.description || null,
467
- props: (decl.attributes || []).map(
468
- (/** @type {Record<string, unknown>} */ a) => ({
469
- name: a.name,
470
- type: /** @type {Record<string, unknown> | undefined} */ (a.type)?.text,
471
- default: a.default,
472
- description: a.description || null,
473
- }),
474
- ),
455
+ props: (decl.attributes || []).map((a: Record<string, unknown>) => ({
456
+ name: a.name,
457
+ type: (a.type as Record<string, unknown> | undefined)?.text,
458
+ default: a.default,
459
+ description: a.description || null,
460
+ })),
475
461
  members: (decl.members || []).filter(
476
- (/** @type {Record<string, unknown>} */ m) =>
462
+ (m: Record<string, unknown>) =>
477
463
  m.kind === "field" && m.privacy !== "private",
478
464
  ),
479
465
  slots: decl.slots || [],
@@ -490,11 +476,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
490
476
  } catch {} // skip if no project package.json
491
477
 
492
478
  return Response.json(components);
493
- } catch (/** @type {unknown} */ e) {
494
- return Response.json(
495
- { error: /** @type {{ message?: string }} */ (e).message },
496
- { status: 500 },
497
- );
479
+ } catch (e) {
480
+ return Response.json({ error: (e as Error).message }, { status: 500 });
498
481
  }
499
482
  }
500
483
 
@@ -538,11 +521,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
538
521
  } catch {}
539
522
  }
540
523
  return Response.json(packages);
541
- } catch (/** @type {unknown} */ e) {
542
- return Response.json(
543
- { error: /** @type {{ message?: string }} */ (e).message },
544
- { status: 500 },
545
- );
524
+ } catch (e) {
525
+ return Response.json({ error: (e as Error).message }, { status: 500 });
546
526
  }
547
527
  }
548
528
 
@@ -567,11 +547,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
567
547
  if (!existsSync(cemPath)) return Response.json({ cem: null });
568
548
  const cem = JSON.parse(await readFile(cemPath, "utf8"));
569
549
  return Response.json({ cem });
570
- } catch (/** @type {unknown} */ e) {
571
- return Response.json(
572
- { error: /** @type {{ message?: string }} */ (e).message },
573
- { status: 500 },
574
- );
550
+ } catch (e) {
551
+ return Response.json({ error: (e as Error).message }, { status: 500 });
575
552
  }
576
553
  }
577
554
 
@@ -596,11 +573,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
596
573
  );
597
574
  }
598
575
  return Response.json({ ok: true });
599
- } catch (/** @type {unknown} */ e) {
600
- return Response.json(
601
- { error: /** @type {{ message?: string }} */ (e).message },
602
- { status: 500 },
603
- );
576
+ } catch (e) {
577
+ return Response.json({ error: (e as Error).message }, { status: 500 });
604
578
  }
605
579
  }
606
580
 
@@ -623,11 +597,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
623
597
  );
624
598
  }
625
599
  return Response.json({ ok: true });
626
- } catch (/** @type {unknown} */ e) {
627
- return Response.json(
628
- { error: /** @type {{ message?: string }} */ (e).message },
629
- { status: 500 },
630
- );
600
+ } catch (e) {
601
+ return Response.json({ error: (e as Error).message }, { status: 500 });
631
602
  }
632
603
  }
633
604
 
@@ -638,21 +609,21 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
638
609
  const abs = fp.startsWith("~") ? fp.replace("~", process.env.HOME || "") : fp;
639
610
  try {
640
611
  assertAccessible(abs, root, activeProjectRoot);
641
- } catch (/** @type {unknown} */ e) {
642
- return new Response(/** @type {{ message?: string }} */ (e).message, { status: 400 });
612
+ } catch (e) {
613
+ return new Response((e as Error).message, { status: 400 });
643
614
  }
644
615
  try {
645
- return Response.json({
646
- content: await readFile(abs, "utf8"),
647
- path: fp,
648
- });
649
- } catch (/** @type {unknown} */ e) {
650
- return /** @type {{ code?: string }} */ (e).code === "ENOENT"
616
+ return Response.json(
617
+ {
618
+ content: await readFile(abs, "utf8"),
619
+ path: fp,
620
+ },
621
+ { headers: { "Cache-Control": "public, max-age=5" } },
622
+ );
623
+ } catch (e) {
624
+ return (e as NodeJS.ErrnoException).code === "ENOENT"
651
625
  ? new Response("Not found", { status: 404 })
652
- : Response.json(
653
- { error: /** @type {{ message?: string }} */ (e).message },
654
- { status: 500 },
655
- );
626
+ : Response.json({ error: (e as Error).message }, { status: 500 });
656
627
  }
657
628
  }
658
629
 
@@ -663,18 +634,15 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
663
634
  const abs = resolve(root, fp);
664
635
  try {
665
636
  assertAccessible(abs, root, activeProjectRoot);
666
- } catch (/** @type {unknown} */ e) {
667
- return new Response(/** @type {{ message?: string }} */ (e).message, { status: 400 });
637
+ } catch (e) {
638
+ return new Response((e as Error).message, { status: 400 });
668
639
  }
669
640
  try {
670
641
  await mkdir(dirname(abs), { recursive: true });
671
642
  await writeFile(abs, await req.text(), "utf8");
672
643
  return Response.json({ ok: true, path: fwd(relative(root, abs)) });
673
- } catch (/** @type {unknown} */ e) {
674
- return Response.json(
675
- { error: /** @type {{ message?: string }} */ (e).message },
676
- { status: 500 },
677
- );
644
+ } catch (e) {
645
+ return Response.json({ error: (e as Error).message }, { status: 500 });
678
646
  }
679
647
  }
680
648
 
@@ -685,19 +653,16 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
685
653
  const abs = resolve(root, fp);
686
654
  try {
687
655
  assertAccessible(abs, root, activeProjectRoot);
688
- } catch (/** @type {unknown} */ e) {
689
- return new Response(/** @type {{ message?: string }} */ (e).message, { status: 400 });
656
+ } catch (e) {
657
+ return new Response((e as Error).message, { status: 400 });
690
658
  }
691
659
  try {
692
660
  await mkdir(dirname(abs), { recursive: true });
693
661
  const buffer = await req.arrayBuffer();
694
662
  await Bun.write(abs, new Uint8Array(buffer));
695
663
  return Response.json({ ok: true, path: fwd(relative(root, abs)) });
696
- } catch (/** @type {unknown} */ e) {
697
- return Response.json(
698
- { error: /** @type {{ message?: string }} */ (e).message },
699
- { status: 500 },
700
- );
664
+ } catch (e) {
665
+ return Response.json({ error: (e as Error).message }, { status: 500 });
701
666
  }
702
667
  }
703
668
 
@@ -708,19 +673,16 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
708
673
  const abs = resolve(root, fp);
709
674
  try {
710
675
  assertAccessible(abs, root, activeProjectRoot);
711
- } catch (/** @type {unknown} */ e) {
712
- return new Response(/** @type {{ message?: string }} */ (e).message, { status: 400 });
676
+ } catch (e) {
677
+ return new Response((e as Error).message, { status: 400 });
713
678
  }
714
679
  try {
715
680
  await unlink(abs);
716
681
  return Response.json({ ok: true, path: fwd(relative(root, abs)) });
717
- } catch (/** @type {unknown} */ e) {
718
- return /** @type {{ code?: string }} */ (e).code === "ENOENT"
682
+ } catch (e) {
683
+ return (e as NodeJS.ErrnoException).code === "ENOENT"
719
684
  ? new Response("Not found", { status: 404 })
720
- : Response.json(
721
- { error: /** @type {{ message?: string }} */ (e).message },
722
- { status: 500 },
723
- );
685
+ : Response.json({ error: (e as Error).message }, { status: 500 });
724
686
  }
725
687
  }
726
688
 
@@ -739,8 +701,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
739
701
  try {
740
702
  assertAccessible(absFrom, root, activeProjectRoot);
741
703
  assertAccessible(absTo, root, activeProjectRoot);
742
- } catch (/** @type {unknown} */ e) {
743
- return new Response(/** @type {{ message?: string }} */ (e).message, { status: 400 });
704
+ } catch (e) {
705
+ return new Response((e as Error).message, { status: 400 });
744
706
  }
745
707
  try {
746
708
  await mkdir(dirname(absTo), { recursive: true });
@@ -750,11 +712,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
750
712
  from: fwd(relative(root, absFrom)),
751
713
  to: fwd(relative(root, absTo)),
752
714
  });
753
- } catch (/** @type {unknown} */ e) {
754
- return Response.json(
755
- { error: /** @type {{ message?: string }} */ (e).message },
756
- { status: 500 },
757
- );
715
+ } catch (e) {
716
+ return Response.json({ error: (e as Error).message }, { status: 500 });
758
717
  }
759
718
  }
760
719
 
@@ -782,11 +741,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
782
741
  path: matches[0],
783
742
  ...(matches.length > 1 ? { alternatives: matches } : {}),
784
743
  });
785
- } catch (/** @type {unknown} */ e) {
786
- return Response.json(
787
- { error: /** @type {{ message?: string }} */ (e).message },
788
- { status: 500 },
789
- );
744
+ } catch (e) {
745
+ return Response.json({ error: (e as Error).message }, { status: 500 });
790
746
  }
791
747
  }
792
748
 
@@ -823,10 +779,10 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
823
779
  moduleAbsPath = serverRequire.resolve(src);
824
780
  }
825
781
  }
826
- } catch (/** @type {unknown} */ e) {
782
+ } catch (e) {
827
783
  return Response.json({
828
784
  schema: null,
829
- error: /** @type {{ message?: string }} */ (e).message,
785
+ error: (e as Error).message,
830
786
  });
831
787
  }
832
788
 
@@ -836,10 +792,10 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
836
792
  const content = readFileSync(moduleAbsPath, "utf8");
837
793
  const classDef = JSON.parse(content);
838
794
  return Response.json({ schema: extractStudioSchema(classDef, moduleAbsPath) });
839
- } catch (/** @type {unknown} */ e) {
795
+ } catch (e) {
840
796
  return Response.json({
841
797
  schema: null,
842
- error: /** @type {{ message?: string }} */ (e).message,
798
+ error: (e as Error).message,
843
799
  });
844
800
  }
845
801
  }
@@ -865,10 +821,10 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
865
821
  return Response.json({ schema: null, error: `Export "${exportName}" not found` });
866
822
  }
867
823
  return Response.json({ schema: ExportedClass.schema ?? null });
868
- } catch (/** @type {unknown} */ e) {
824
+ } catch (e) {
869
825
  return Response.json({
870
826
  schema: null,
871
- error: /** @type {{ message?: string }} */ (e).message,
827
+ error: (e as Error).message,
872
828
  });
873
829
  }
874
830
  }
@@ -879,7 +835,7 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
879
835
  const cwd = activeProjectRoot || root;
880
836
  const gitCmd = path.slice("/__studio/git/".length);
881
837
 
882
- const runGit = async (/** @type {string[]} */ args) => {
838
+ const runGit = async (args: string[]) => {
883
839
  const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
884
840
  const exitCode = await proc.exited;
885
841
  const stdout = await new Response(proc.stdout).text();
@@ -913,9 +869,12 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
913
869
  runGit(["remote"]),
914
870
  ]);
915
871
  const status = parseGitStatus(out);
916
- status.isRepo = true;
917
- status.remotes = remotesOut.trim().split("\n").filter(Boolean);
918
- return Response.json(status);
872
+ const fullStatus = {
873
+ ...status,
874
+ isRepo: true,
875
+ remotes: remotesOut.trim().split("\n").filter(Boolean),
876
+ };
877
+ return Response.json(fullStatus);
919
878
  }
920
879
 
921
880
  if (gitCmd === "init" && req.method === "POST") {
@@ -991,11 +950,11 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
991
950
  }
992
951
 
993
952
  if (gitCmd === "push" && req.method === "POST") {
994
- let body = {};
953
+ let body: Record<string, unknown> = {};
995
954
  try {
996
955
  body = await req.json();
997
956
  } catch {}
998
- const { setUpstream } = /** @type {{ setUpstream?: boolean }} */ (body);
957
+ const { setUpstream } = body as { setUpstream?: boolean };
999
958
  if (setUpstream) {
1000
959
  const branch = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
1001
960
  await runGit(["push", "-u", "origin", branch]);
@@ -1075,11 +1034,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
1075
1034
  if (exitCode !== 0) throw new Error(stderr || `git clone exited with ${exitCode}`);
1076
1035
  return Response.json({ ok: true, root: dest });
1077
1036
  }
1078
- } catch (/** @type {unknown} */ e) {
1079
- return Response.json(
1080
- { error: /** @type {{ message?: string }} */ (e).message },
1081
- { status: 500 },
1082
- );
1037
+ } catch (e) {
1038
+ return Response.json({ error: (e as Error).message }, { status: 500 });
1083
1039
  }
1084
1040
  }
1085
1041
 
@@ -1098,8 +1054,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
1098
1054
  systemPrompt: body.systemPrompt,
1099
1055
  });
1100
1056
  return Response.json(result);
1101
- } catch (/** @type {any} */ e) {
1102
- return Response.json({ error: e.message }, { status: 500 });
1057
+ } catch (e) {
1058
+ return Response.json({ error: e instanceof Error ? e.message : String(e) }, { status: 500 });
1103
1059
  }
1104
1060
  }
1105
1061
 
@@ -1122,8 +1078,8 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
1122
1078
  const body = await req.json();
1123
1079
  claude.sendMessage(id, body.message);
1124
1080
  return Response.json({ ok: true });
1125
- } catch (/** @type {any} */ e) {
1126
- return Response.json({ error: e.message }, { status: 500 });
1081
+ } catch (e) {
1082
+ return Response.json({ error: e instanceof Error ? e.message : String(e) }, { status: 500 });
1127
1083
  }
1128
1084
  }
1129
1085
 
@@ -1162,7 +1118,7 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
1162
1118
  * required: string[];
1163
1119
  * }}
1164
1120
  */
1165
- function extractStudioSchema(classDef, classJsonPath) {
1121
+ function extractStudioSchema(classDef: ClassJsonDef, classJsonPath: string) {
1166
1122
  // If extends.$ref points to a parent, recursively merge
1167
1123
  let parentSchema = null;
1168
1124
  if (classDef.extends && typeof classDef.extends === "object" && classDef.extends.$ref) {
@@ -1178,10 +1134,8 @@ function extractStudioSchema(classDef, classJsonPath) {
1178
1134
 
1179
1135
  const params = classDef.$defs?.parameters ?? {};
1180
1136
  const fields = classDef.$defs?.fields ?? {};
1181
- /** @type {Record<string, Record<string, unknown>>} */
1182
- const properties = {};
1183
- /** @type {string[]} */
1184
- const required = [];
1137
+ const properties: Record<string, Record<string, unknown>> = {};
1138
+ const required: string[] = [];
1185
1139
 
1186
1140
  // Start with parent properties (child overrides)
1187
1141
  if (parentSchema?.properties) {
@@ -1194,8 +1148,7 @@ function extractStudioSchema(classDef, classJsonPath) {
1194
1148
  // Build properties from parameters (constructor config surface)
1195
1149
  for (const [key, param] of Object.entries(params)) {
1196
1150
  const id = param.identifier ?? key;
1197
- /** @type {Record<string, unknown>} */
1198
- const prop = {};
1151
+ const prop: Record<string, unknown> = {};
1199
1152
  if (param.type && typeof param.type === "object") Object.assign(prop, param.type);
1200
1153
  if (param.description) prop.description = param.description;
1201
1154
  if (param.examples) prop.examples = param.examples;
@@ -1208,8 +1161,7 @@ function extractStudioSchema(classDef, classJsonPath) {
1208
1161
  if (field.role !== "field") continue;
1209
1162
  if (field.access === "private") continue;
1210
1163
  const id = field.identifier ?? key;
1211
- /** @type {Record<string, unknown>} */
1212
- const prop = {};
1164
+ const prop: Record<string, unknown> = {};
1213
1165
  if (field.type && typeof field.type === "object") Object.assign(prop, field.type);
1214
1166
  if (field.description) prop.description = field.description;
1215
1167
  if (field.default !== undefined) prop.default = field.default;
@@ -1221,8 +1173,7 @@ function extractStudioSchema(classDef, classJsonPath) {
1221
1173
 
1222
1174
  // Determine required from constructor parameters that have no default
1223
1175
  const ctorParams = classDef.$defs?.constructor?.parameters ?? [];
1224
- /** @type {Set<string>} */
1225
- const requiredSet = new Set(required);
1176
+ const requiredSet: Set<string> = new Set(required);
1226
1177
  for (const p of ctorParams) {
1227
1178
  const name = p.$ref ? p.$ref.split("/").pop() : (p.identifier ?? p.name);
1228
1179
  if (name && properties[name] && properties[name].default === undefined) {
@@ -1230,9 +1181,12 @@ function extractStudioSchema(classDef, classJsonPath) {
1230
1181
  }
1231
1182
  }
1232
1183
 
1184
+ const resolveMethod = classDef.$defs?.methods?.resolve;
1185
+
1233
1186
  return {
1234
1187
  description: classDef.description ?? classDef.title,
1235
1188
  properties,
1236
1189
  required: [...requiredSet],
1190
+ ...(resolveMethod?.returns ? { returns: resolveMethod.returns } : {}),
1237
1191
  };
1238
1192
  }
package/src/types.ts ADDED
@@ -0,0 +1,61 @@
1
+ export interface BuildEntry {
2
+ entrypoints: string[];
3
+ outdir: string;
4
+ match?: ((path: string) => boolean) | RegExp;
5
+ label?: string;
6
+ }
7
+
8
+ export interface ClassJsonParam {
9
+ $ref?: string;
10
+ identifier?: string;
11
+ name?: string;
12
+ }
13
+
14
+ export interface ClassJsonField {
15
+ role?: string;
16
+ access?: string;
17
+ scope?: string;
18
+ identifier?: string;
19
+ initializer?: unknown;
20
+ default?: unknown;
21
+ type?: Record<string, unknown>;
22
+ description?: string;
23
+ examples?: unknown[];
24
+ }
25
+
26
+ export interface ClassJsonMethod {
27
+ identifier?: string;
28
+ role?: string;
29
+ scope?: string;
30
+ body?: string | string[];
31
+ getter?: { body: string };
32
+ setter?: { parameters?: ClassJsonParam[]; body: string };
33
+ parameters?: ClassJsonParam[];
34
+ returns?: Record<string, unknown>;
35
+ }
36
+
37
+ export interface ClassJsonParameterDef {
38
+ identifier?: string;
39
+ type?: Record<string, unknown>;
40
+ format?: string;
41
+ description?: string;
42
+ examples?: unknown[];
43
+ }
44
+
45
+ export interface ClassJsonDef {
46
+ title?: string;
47
+ description?: string;
48
+ $implementation?: string;
49
+ extends?: { $ref?: string };
50
+ $defs?: {
51
+ fields?: Record<string, ClassJsonField>;
52
+ constructor?: {
53
+ role?: string;
54
+ $prototype?: string;
55
+ body?: string | string[];
56
+ parameters?: ClassJsonParam[];
57
+ };
58
+ methods?: Record<string, ClassJsonMethod>;
59
+ parameters?: Record<string, ClassJsonParameterDef>;
60
+ };
61
+ }