@jxsuite/server 0.23.2 → 0.24.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/server",
3
- "version": "0.23.2",
3
+ "version": "0.24.0",
4
4
  "description": "Jx development server with live reload, server-side proxy, and studio integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,8 +13,9 @@
13
13
  ],
14
14
  "type": "module",
15
15
  "exports": {
16
- ".": "./src/server.js",
17
- "./claude-session": "./src/claude-session.js"
16
+ ".": "./src/server.ts",
17
+ "./claude-session": "./src/claude-session.ts",
18
+ "./types": "./src/types.ts"
18
19
  },
19
20
  "publishConfig": {
20
21
  "provenance": true
@@ -25,11 +26,13 @@
25
26
  "test:coverage": "bun test --isolate --coverage"
26
27
  },
27
28
  "dependencies": {
28
- "@anthropic-ai/claude-agent-sdk": "^0.3.159",
29
+ "@anthropic-ai/claude-agent-sdk": "^0.3.160",
29
30
  "@anthropic-ai/sdk": "^0.100.1",
30
- "@jxsuite/compiler": "^0.23.2",
31
+ "@jxsuite/compiler": "^0.24.0",
31
32
  "@jxsuite/create": "^0.19.0",
32
- "@jxsuite/parser": "^0.23.2",
33
+ "@jxsuite/parser": "^0.24.0",
34
+ "@jxsuite/runtime": "^0.24.0",
35
+ "@jxsuite/schema": "^0.24.0",
33
36
  "@modelcontextprotocol/sdk": "^1.29.0",
34
37
  "chokidar": "^5.0.0",
35
38
  "zod": "^4.4.3"
@@ -10,7 +10,14 @@
10
10
  * label?: string;
11
11
  * }[]} builds
12
12
  */
13
- export async function buildAll(builds) {
13
+ export async function buildAll(
14
+ builds: {
15
+ entrypoints: string[];
16
+ outdir: string;
17
+ match?: ((path: string) => boolean) | RegExp;
18
+ label?: string;
19
+ }[],
20
+ ) {
14
21
  for (const entry of builds) {
15
22
  const { match: _match, label, ...opts } = entry;
16
23
  const result = await Bun.build({
@@ -36,7 +43,15 @@ export async function buildAll(builds) {
36
43
  * @param {string} changedFile
37
44
  * @returns {Promise<{ rebuilt: string[]; success: boolean }>}
38
45
  */
39
- export async function rebuild(builds, changedFile) {
46
+ export async function rebuild(
47
+ builds: {
48
+ entrypoints: string[];
49
+ outdir: string;
50
+ match?: ((path: string) => boolean) | RegExp;
51
+ label?: string;
52
+ }[],
53
+ changedFile: string,
54
+ ) {
40
55
  const rebuilt = [];
41
56
  let ok = true;
42
57
  for (const entry of builds) {
@@ -8,8 +8,19 @@
8
8
 
9
9
  import { query } from "@anthropic-ai/claude-agent-sdk";
10
10
 
11
- /** @typedef {import("@anthropic-ai/claude-agent-sdk").SDKMessage} SDKMessage */
12
- /** @typedef {import("@anthropic-ai/claude-agent-sdk").Options} AgentOptions */
11
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
12
+ import type { Options as AgentOptions } from "@anthropic-ai/claude-agent-sdk";
13
+
14
+ interface Session {
15
+ id: string;
16
+ sessionId: string | null;
17
+ projectRoot: string;
18
+ messages: SDKMessage[];
19
+ sseClients: Set<(event: string, data: unknown) => void>;
20
+ status: "active" | "idle" | "stopped";
21
+ abortController: AbortController | null;
22
+ query: AsyncGenerator | null;
23
+ }
13
24
 
14
25
  /**
15
26
  * Build a clean env for SDK child processes. Strips session-specific vars from a parent Claude Code
@@ -28,21 +39,7 @@ function buildSdkEnv() {
28
39
  return env;
29
40
  }
30
41
 
31
- /**
32
- * @typedef {{
33
- * id: string;
34
- * sessionId: string | null;
35
- * projectRoot: string;
36
- * messages: SDKMessage[];
37
- * sseClients: Set<(event: string, data: unknown) => void>;
38
- * status: "active" | "idle" | "stopped";
39
- * abortController: AbortController | null;
40
- * query: AsyncGenerator | null;
41
- * }} Session
42
- */
43
-
44
- /** @type {Map<string, Session>} */
45
- const sessions = new Map();
42
+ const sessions: Map<string, Session> = new Map();
46
43
 
47
44
  let idCounter = 0;
48
45
 
@@ -57,8 +54,8 @@ function genId() {
57
54
  * @param {string} event
58
55
  * @param {unknown} data
59
56
  */
60
- function broadcast(session, event, data) {
61
- session.messages.push(/** @type {any} */ (data));
57
+ function broadcast(session: Session, event: string, data: unknown) {
58
+ session.messages.push(data as SDKMessage);
62
59
  for (const send of session.sseClients) {
63
60
  send(event, data);
64
61
  }
@@ -70,18 +67,18 @@ function broadcast(session, event, data) {
70
67
  * @param {Session} session
71
68
  * @param {AsyncGenerator<SDKMessage>} stream
72
69
  */
73
- async function processStream(session, stream) {
70
+ async function processStream(session: Session, stream: AsyncGenerator<SDKMessage>) {
74
71
  session.status = "active";
75
72
  try {
76
73
  for await (const message of stream) {
77
- if (/** @type {string} */ (session.status) === "stopped") break;
74
+ if ((session.status as string) === "stopped") break;
78
75
  if (message.session_id && !session.sessionId) {
79
76
  session.sessionId = message.session_id;
80
77
  }
81
78
  broadcast(session, message.type, message);
82
79
  }
83
80
  } catch (err) {
84
- if (/** @type {any} */ (err).name !== "AbortError") {
81
+ if (err instanceof Error && err.name !== "AbortError") {
85
82
  broadcast(session, "error", { type: "error", error: String(err) });
86
83
  }
87
84
  } finally {
@@ -100,12 +97,15 @@ async function processStream(session, stream) {
100
97
  * @param {{ systemPrompt?: string }} [opts]
101
98
  * @returns {{ id: string }}
102
99
  */
103
- export function createSession(projectRoot, message, opts = {}) {
100
+ export function createSession(
101
+ projectRoot: string,
102
+ message: string,
103
+ opts: { systemPrompt?: string } = {},
104
+ ) {
104
105
  const id = genId();
105
106
  const abortController = new AbortController();
106
107
 
107
- /** @type {Session} */
108
- const session = {
108
+ const session: Session = {
109
109
  id,
110
110
  sessionId: null,
111
111
  projectRoot,
@@ -118,8 +118,7 @@ export function createSession(projectRoot, message, opts = {}) {
118
118
 
119
119
  sessions.set(id, session);
120
120
 
121
- /** @type {AgentOptions} */
122
- const queryOpts = {
121
+ const queryOpts: AgentOptions = {
123
122
  cwd: projectRoot,
124
123
  abortController,
125
124
  permissionMode: "acceptEdits",
@@ -147,7 +146,7 @@ export function createSession(projectRoot, message, opts = {}) {
147
146
  * @param {string} id
148
147
  * @param {string} message
149
148
  */
150
- export function sendMessage(id, message) {
149
+ export function sendMessage(id: string, message: string) {
151
150
  const session = sessions.get(id);
152
151
  if (!session) throw new Error(`Session not found: ${id}`);
153
152
  if (session.status === "active") throw new Error("Session is still processing");
@@ -155,8 +154,7 @@ export function sendMessage(id, message) {
155
154
  const abortController = new AbortController();
156
155
  session.abortController = abortController;
157
156
 
158
- /** @type {AgentOptions} */
159
- const queryOpts = {
157
+ const queryOpts: AgentOptions = {
160
158
  cwd: session.projectRoot,
161
159
  abortController,
162
160
  permissionMode: "acceptEdits",
@@ -182,7 +180,7 @@ export function sendMessage(id, message) {
182
180
  *
183
181
  * @param {string} id
184
182
  */
185
- export function stopSession(id) {
183
+ export function stopSession(id: string) {
186
184
  const session = sessions.get(id);
187
185
  if (!session) return;
188
186
  session.status = "stopped";
@@ -196,7 +194,7 @@ export function stopSession(id) {
196
194
  *
197
195
  * @param {string} id
198
196
  */
199
- export function deleteSession(id) {
197
+ export function deleteSession(id: string) {
200
198
  stopSession(id);
201
199
  sessions.delete(id);
202
200
  }
@@ -207,19 +205,18 @@ export function deleteSession(id) {
207
205
  * @param {string} id
208
206
  * @returns {Response}
209
207
  */
210
- export function streamSession(id) {
208
+ export function streamSession(id: string) {
211
209
  const session = sessions.get(id);
212
210
  if (!session) {
213
211
  return new Response("Session not found", { status: 404 });
214
212
  }
215
213
 
216
214
  const encoder = new TextEncoder();
217
- /** @type {(event: string, data: unknown) => void} */
218
- let sendRef = () => {};
215
+ let sendRef: (event: string, data: unknown) => void = () => {};
219
216
  const stream = new ReadableStream({
220
217
  start(controller) {
221
218
  /** @param {string} event @param {unknown} data */
222
- sendRef = (event, data) => {
219
+ sendRef = (event: string, data: unknown) => {
223
220
  try {
224
221
  controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
225
222
  } catch {
@@ -230,7 +227,7 @@ export function streamSession(id) {
230
227
  session.sseClients.add(sendRef);
231
228
 
232
229
  for (const msg of session.messages) {
233
- sendRef(/** @type {any} */ (msg).type || "message", msg);
230
+ sendRef(((msg as Record<string, unknown>).type as string) || "message", msg);
234
231
  }
235
232
 
236
233
  const heartbeat = setInterval(() => {
@@ -278,9 +275,13 @@ export async function getAuthStatus() {
278
275
 
279
276
  for await (const msg of testStream) {
280
277
  if (msg.type === "assistant") {
281
- const content = /** @type {any} */ (msg).message?.content;
282
- if (Array.isArray(content)) {
283
- const text = content.find((b) => b.type === "text");
278
+ const content = (msg as Record<string, unknown>).message as
279
+ | { content?: unknown[] }
280
+ | undefined;
281
+ if (Array.isArray(content?.content)) {
282
+ const text = content.content.find((b: any) => b.type === "text") as
283
+ | { text?: string }
284
+ | undefined;
284
285
  if (text?.text?.toLowerCase().includes("error")) {
285
286
  clearTimeout(timeout);
286
287
  ctrl.abort();
@@ -292,11 +293,11 @@ export async function getAuthStatus() {
292
293
  return { authenticated: true };
293
294
  }
294
295
  if (msg.type === "result") {
295
- const result = /** @type {any} */ (msg);
296
+ const result = msg as Record<string, unknown>;
296
297
  clearTimeout(timeout);
297
298
  ctrl.abort();
298
299
  if (result.is_error) {
299
- return { authenticated: false, error: result.result || "API error" };
300
+ return { authenticated: false, error: (result.result as string) || "API error" };
300
301
  }
301
302
  return { authenticated: true };
302
303
  }
@@ -314,7 +315,7 @@ export async function getAuthStatus() {
314
315
  *
315
316
  * @param {string} id
316
317
  */
317
- export function getSession(id) {
318
+ export function getSession(id: string) {
318
319
  const session = sessions.get(id);
319
320
  if (!session) return null;
320
321
  return { id: session.id, status: session.status, messageCount: session.messages.length };
@@ -10,14 +10,15 @@ import { join, resolve } from "node:path";
10
10
  import { unlink } from "node:fs/promises";
11
11
  import { format } from "oxfmt";
12
12
 
13
- /**
14
- * @typedef {{
15
- * span: { offset: number; line: number; [key: string]: unknown };
16
- * [key: string]: unknown;
17
- * }} OxcLabel
18
- *
19
- * @typedef {{ labels?: OxcLabel[]; [key: string]: unknown }} OxcDiagnostic
20
- */
13
+ interface OxcLabel {
14
+ span: { offset: number; line: number; [key: string]: unknown };
15
+ [key: string]: unknown;
16
+ }
17
+
18
+ interface OxcDiagnostic {
19
+ labels?: OxcLabel[];
20
+ [key: string]: unknown;
21
+ }
21
22
 
22
23
  const OXLINT_BIN = resolve(
23
24
  import.meta.dir,
@@ -31,13 +32,13 @@ const OXLINT_BIN = resolve(
31
32
  * @param {string} body
32
33
  * @param {string[]} [args]
33
34
  */
34
- function wrapBody(body, args = ["state", "event"]) {
35
+ function wrapBody(body: string, args: string[] = ["state", "event"]) {
35
36
  const params = args.join(", ");
36
37
  return `function __jx_fn__(${params}) {\n${body}\n}`;
37
38
  }
38
39
 
39
40
  /** @param {string} formatted */
40
- function unwrapFormatted(formatted) {
41
+ function unwrapFormatted(formatted: string) {
41
42
  const lines = formatted.split("\n");
42
43
  // Remove first line (function header) and last non-empty line (closing brace)
43
44
  let end = lines.length - 1;
@@ -52,7 +53,7 @@ function unwrapFormatted(formatted) {
52
53
  * @param {OxcDiagnostic[]} diagnostics
53
54
  * @param {number} headerLen
54
55
  */
55
- function adjustDiagnostics(diagnostics, headerLen) {
56
+ function adjustDiagnostics(diagnostics: OxcDiagnostic[], headerLen: number) {
56
57
  return diagnostics
57
58
  .filter((d) => {
58
59
  const line = d.labels?.[0]?.span?.line;
@@ -81,7 +82,7 @@ const minifier = new Bun.Transpiler({ minifyWhitespace: true });
81
82
  * @param {Request} req
82
83
  * @param {URL} url
83
84
  */
84
- export async function handleCodeApi(req, url) {
85
+ export async function handleCodeApi(req: Request, url: URL) {
85
86
  const path = url.pathname;
86
87
  if (!path.startsWith("/__studio/code/") || req.method !== "POST") return null;
87
88
 
@@ -107,10 +108,10 @@ export async function handleCodeApi(req, url) {
107
108
  code: unwrapFormatted(result.code),
108
109
  errors: result.errors,
109
110
  });
110
- } catch (/** @type {unknown} */ e) {
111
+ } catch (e) {
111
112
  return Response.json({
112
113
  code,
113
- errors: [{ message: /** @type {{ message?: string }} */ (e).message }],
114
+ errors: [{ message: (e as Error).message }],
114
115
  });
115
116
  }
116
117
  }
@@ -124,8 +125,8 @@ export async function handleCodeApi(req, url) {
124
125
  try {
125
126
  const minified = minifier.transformSync(code).trim();
126
127
  return Response.json({ code: minified });
127
- } catch (/** @type {unknown} */ e) {
128
- return Response.json({ code, error: /** @type {{ message?: string }} */ (e).message });
128
+ } catch (e) {
129
+ return Response.json({ code, error: (e as Error).message });
129
130
  }
130
131
  }
131
132
 
@@ -154,10 +155,10 @@ export async function handleCodeApi(req, url) {
154
155
  const parsed = JSON.parse(output);
155
156
  const adjusted = adjustDiagnostics(parsed.diagnostics || [], headerLen);
156
157
  return Response.json({ diagnostics: adjusted });
157
- } catch (/** @type {unknown} */ e) {
158
+ } catch (e) {
158
159
  return Response.json({
159
160
  diagnostics: [],
160
- error: /** @type {{ message?: string }} */ (e).message,
161
+ error: (e as Error).message,
161
162
  });
162
163
  } finally {
163
164
  try {
@@ -4,6 +4,8 @@ import { resolve, relative, dirname } from "node:path";
4
4
  import { readFileSync, existsSync } from "node:fs";
5
5
  import { createRequire } from "node:module";
6
6
  import { loadContentTypes } from "@jxsuite/compiler/content-loader";
7
+ import type { DynamicClass } from "@jxsuite/runtime/types";
8
+ import type { ClassJsonDef } from "./types.ts";
7
9
 
8
10
  /**
9
11
  * Lazy-load project context (project.json + content types) for class instantiation.
@@ -15,7 +17,7 @@ import { loadContentTypes } from "@jxsuite/compiler/content-loader";
15
17
  * root: string;
16
18
  * } | null>}
17
19
  */
18
- async function loadProjectContext(projectRoot) {
20
+ async function loadProjectContext(projectRoot: string) {
19
21
  const projectJsonPath = resolve(projectRoot, "project.json");
20
22
  if (!existsSync(projectJsonPath)) return null;
21
23
  try {
@@ -36,7 +38,11 @@ async function loadProjectContext(projectRoot) {
36
38
  * @param {string} root
37
39
  * @param {string | null} [activeProjectRoot]
38
40
  */
39
- export async function handleResolve(req, root, activeProjectRoot = null) {
41
+ export async function handleResolve(
42
+ req: Request,
43
+ root: string,
44
+ activeProjectRoot: string | null = null,
45
+ ) {
40
46
  let body;
41
47
  try {
42
48
  body = await req.json();
@@ -69,11 +75,8 @@ export async function handleResolve(req, root, activeProjectRoot = null) {
69
75
  moduleAbsPath = serverRequire.resolve($src);
70
76
  }
71
77
  }
72
- } catch (/** @type {unknown} */ e) {
73
- return new Response(
74
- `Cannot resolve $src "${$src}": ${/** @type {{ message?: string }} */ (e).message}`,
75
- { status: 400 },
76
- );
78
+ } catch (e) {
79
+ return new Response(`Cannot resolve $src "${$src}": ${(e as Error).message}`, { status: 400 });
77
80
  }
78
81
 
79
82
  // Rebase relative config paths from doc-relative to CWD-relative
@@ -125,9 +128,7 @@ export async function handleResolve(req, root, activeProjectRoot = null) {
125
128
 
126
129
  // Self-contained: construct class from schema
127
130
  const DynClass = classFromSchema(classDef);
128
- const instance = /** @type {{ resolve?: () => unknown; value?: unknown }} */ (
129
- new DynClass(config)
130
- );
131
+ const instance = new DynClass(config) as { resolve?: () => unknown; value?: unknown };
131
132
  const value =
132
133
  typeof instance.resolve === "function"
133
134
  ? await instance.resolve()
@@ -135,11 +136,8 @@ export async function handleResolve(req, root, activeProjectRoot = null) {
135
136
  ? instance.value
136
137
  : instance;
137
138
  return Response.json(value);
138
- } catch (/** @type {unknown} */ e) {
139
- return Response.json(
140
- { error: /** @type {{ message?: string }} */ (e).message },
141
- { status: 500 },
142
- );
139
+ } catch (e) {
140
+ return Response.json({ error: (e as Error).message }, { status: 500 });
143
141
  }
144
142
  }
145
143
 
@@ -158,7 +156,7 @@ export async function handleResolve(req, root, activeProjectRoot = null) {
158
156
  * @param {Request} req
159
157
  * @param {string} root
160
158
  */
161
- export async function handleServerFunction(req, root) {
159
+ export async function handleServerFunction(req: Request, root: string) {
162
160
  let body;
163
161
  try {
164
162
  body = await req.json();
@@ -178,8 +176,8 @@ export async function handleServerFunction(req, root) {
178
176
  } else {
179
177
  moduleAbsPath = resolve(root, $src);
180
178
  }
181
- } catch (/** @type {unknown} */ e) {
182
- return new Response(`Cannot resolve $src: ${/** @type {{ message?: string }} */ (e).message}`, {
179
+ } catch (e) {
180
+ return new Response(`Cannot resolve $src: ${(e as Error).message}`, {
183
181
  status: 400,
184
182
  });
185
183
  }
@@ -187,11 +185,8 @@ export async function handleServerFunction(req, root) {
187
185
  let mod;
188
186
  try {
189
187
  mod = await import(moduleAbsPath);
190
- } catch (/** @type {unknown} */ e) {
191
- return new Response(
192
- `Failed to import "${$src}": ${/** @type {{ message?: string }} */ (e).message}`,
193
- { status: 500 },
194
- );
188
+ } catch (e) {
189
+ return new Response(`Failed to import "${$src}": ${(e as Error).message}`, { status: 500 });
195
190
  }
196
191
 
197
192
  const fn = mod[xport] ?? mod.default?.[xport];
@@ -202,11 +197,8 @@ export async function handleServerFunction(req, root) {
202
197
  try {
203
198
  const result = await fn(args);
204
199
  return Response.json(result ?? null);
205
- } catch (/** @type {unknown} */ e) {
206
- return Response.json(
207
- { error: /** @type {{ message?: string }} */ (e).message },
208
- { status: 500 },
209
- );
200
+ } catch (e) {
201
+ return Response.json({ error: (e as Error).message }, { status: 500 });
210
202
  }
211
203
  }
212
204
 
@@ -216,15 +208,15 @@ export async function handleServerFunction(req, root) {
216
208
  *
217
209
  * @param {ClassJsonDef} classDef
218
210
  */
219
- function classFromSchema(classDef) {
211
+ function classFromSchema(classDef: ClassJsonDef) {
220
212
  const fields = classDef.$defs?.fields ?? {};
221
213
  const ctor = classDef.$defs?.constructor;
222
214
  const methods = classDef.$defs?.methods ?? {};
223
215
 
224
216
  class DynClass {
225
217
  constructor(config = {}) {
226
- const self = /** @type {Record<string, unknown>} */ (this);
227
- const cfg = /** @type {Record<string, unknown>} */ (config);
218
+ const self = this as Record<string, unknown>;
219
+ const cfg = config as Record<string, unknown>;
228
220
  for (const [key, field] of Object.entries(fields)) {
229
221
  const id = field.identifier ?? key;
230
222
  if (cfg[id] !== undefined) self[id] = cfg[id];
@@ -248,21 +240,17 @@ function classFromSchema(classDef) {
248
240
  const bodyStr = Array.isArray(method.body) ? method.body.join("\n") : (method.body ?? "");
249
241
 
250
242
  if (method.role === "accessor") {
251
- /** @type {PropertyDescriptor} */
252
- const descriptor = {};
253
- if (method.getter)
254
- descriptor.get = /** @type {() => unknown} */ (new Function(method.getter.body));
243
+ const descriptor: PropertyDescriptor = {};
244
+ if (method.getter) descriptor.get = new Function(method.getter.body) as () => unknown;
255
245
  if (method.setter) {
256
246
  const sp = (method.setter.parameters ?? []).map((p) => p.$ref?.split("/").pop() ?? "v");
257
- descriptor.set = /** @type {(v: unknown) => void} */ (
258
- new Function(...sp, method.setter.body)
259
- );
247
+ descriptor.set = new Function(...sp, method.setter.body) as (v: unknown) => void;
260
248
  }
261
249
  Object.defineProperty(DynClass.prototype, name, { ...descriptor, configurable: true });
262
250
  } else if (method.scope === "static") {
263
- /** @type {DynamicClass} */ (DynClass)[name] = new Function(...params, bodyStr);
251
+ (DynClass as DynamicClass)[name] = new Function(...params, bodyStr);
264
252
  } else {
265
- /** @type {DynamicClass} */ (DynClass).prototype[name] = new Function(...params, bodyStr);
253
+ (DynClass as DynamicClass).prototype[name] = new Function(...params, bodyStr);
266
254
  }
267
255
  }
268
256
 
@@ -14,11 +14,11 @@
14
14
  */
15
15
 
16
16
  import { resolve, join } from "node:path";
17
- import { buildAll } from "./build.js";
18
- import { createWatcher, injectSSE } from "./watch.js";
19
- import { handleResolve, handleServerFunction } from "./resolve.js";
20
- import { handleStudioApi } from "./studio-api.js";
21
- import { handleCodeApi } from "./code-api.js";
17
+ import { buildAll } from "./build.ts";
18
+ import { createWatcher, injectSSE } from "./watch.ts";
19
+ import { handleResolve, handleServerFunction } from "./resolve.ts";
20
+ import { handleStudioApi } from "./studio-api.ts";
21
+ import { handleCodeApi } from "./code-api.ts";
22
22
  import { existsSync, readFileSync } from "node:fs";
23
23
 
24
24
  /**
@@ -30,7 +30,7 @@ import { existsSync, readFileSync } from "node:fs";
30
30
  * @param {string} urlPath - URL pathname (e.g. "/pages/@jxsuite/parser/Foo.class.json")
31
31
  * @returns {string | null} Absolute file path or null
32
32
  */
33
- function resolveNpmPath(root, urlPath) {
33
+ function resolveNpmPath(root: string, urlPath: string) {
34
34
  let segments = urlPath.split("/").filter(Boolean);
35
35
 
36
36
  // If "node_modules" appears in the path, use everything before it as a subdirectory
@@ -53,10 +53,8 @@ function resolveNpmPath(root, urlPath) {
53
53
  }
54
54
  }
55
55
 
56
- /** @type {string} */
57
- let pkgDir = "";
58
- /** @type {string} */
59
- let subpath = "";
56
+ let pkgDir: string = "";
57
+ let subpath: string = "";
60
58
 
61
59
  if (isScoped) {
62
60
  if (start < 0 || start + 1 >= segments.length) return null;
@@ -142,7 +140,19 @@ export { resolveNpmPath };
142
140
  * @param {Function} [options.middleware] - Custom route handler (req, url) => Response|null
143
141
  * @returns {Promise<object>} The Bun.serve server object
144
142
  */
145
- export async function createDevServer(options) {
143
+ export async function createDevServer(options: {
144
+ root: string;
145
+ port?: number;
146
+ builds?: {
147
+ entrypoints: string[];
148
+ outdir: string;
149
+ match?: ((path: string) => boolean) | RegExp;
150
+ label?: string;
151
+ }[];
152
+ watch?: boolean | object;
153
+ studio?: boolean;
154
+ middleware?: (req: Request, url: URL) => Response | null | Promise<Response | null>;
155
+ }) {
146
156
  const {
147
157
  root,
148
158
  port = 3000,
@@ -171,12 +181,10 @@ export async function createDevServer(options) {
171
181
  }
172
182
 
173
183
  // Bundle cache for npm packages (bare specifier → bundled JS)
174
- /** @type {Map<string, string>} */
175
- const bundleCache = new Map();
184
+ const bundleCache: Map<string, string> = new Map();
176
185
 
177
186
  // Active studio project root (set via /__studio/activate, used for static file fallback)
178
- /** @type {string | null} */
179
- let activeProjectRoot = null;
187
+ let activeProjectRoot: string | null = null;
180
188
 
181
189
  // ─── HTTP server ────────────────────────────────────────────────────────────
182
190
 
@@ -185,7 +193,7 @@ export async function createDevServer(options) {
185
193
 
186
194
  async fetch(req) {
187
195
  const url = new URL(req.url);
188
- let path = url.pathname;
196
+ let path = decodeURIComponent(url.pathname);
189
197
  if (path.endsWith("/")) path += "index.html";
190
198
  else if (path === "") path = "/index.html";
191
199
 
@@ -270,7 +278,7 @@ export async function createDevServer(options) {
270
278
  if (result.success && result.outputs.length > 0) {
271
279
  bundleCache.set(cacheKey, await result.outputs[0].text());
272
280
  }
273
- } catch (/** @type {unknown} */ e) {
281
+ } catch (e) {
274
282
  console.error("Bundle failed for", resolved, e);
275
283
  }
276
284
  }