@jxsuite/server 0.23.1 → 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 +9 -6
- package/src/{build.js → build.ts} +17 -2
- package/src/{claude-session.js → claude-session.ts} +44 -43
- package/src/{code-api.js → code-api.ts} +19 -18
- package/src/{resolve.js → resolve.ts} +28 -40
- package/src/{server.js → server.ts} +25 -17
- package/src/{studio-api.js → studio-api.ts} +122 -168
- package/src/types.ts +61 -0
- package/src/{watch.js → watch.ts} +14 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/server",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
17
|
-
"./claude-session": "./src/claude-session.
|
|
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.
|
|
29
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.160",
|
|
29
30
|
"@anthropic-ai/sdk": "^0.100.1",
|
|
30
|
-
"@jxsuite/compiler": "^0.
|
|
31
|
+
"@jxsuite/compiler": "^0.24.0",
|
|
31
32
|
"@jxsuite/create": "^0.19.0",
|
|
32
|
-
"@jxsuite/parser": "^0.
|
|
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(
|
|
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(
|
|
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
|
-
|
|
12
|
-
|
|
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(
|
|
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 (
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
282
|
-
|
|
283
|
-
|
|
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 =
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 (
|
|
111
|
+
} catch (e) {
|
|
111
112
|
return Response.json({
|
|
112
113
|
code,
|
|
113
|
-
errors: [{ 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 (
|
|
128
|
-
return Response.json({ code, error:
|
|
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 (
|
|
158
|
+
} catch (e) {
|
|
158
159
|
return Response.json({
|
|
159
160
|
diagnostics: [],
|
|
160
|
-
error:
|
|
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(
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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 (
|
|
182
|
-
return new Response(`Cannot resolve $src: ${
|
|
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 (
|
|
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 (
|
|
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 =
|
|
227
|
-
const cfg =
|
|
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
|
-
|
|
252
|
-
|
|
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 =
|
|
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
|
-
|
|
251
|
+
(DynClass as DynamicClass)[name] = new Function(...params, bodyStr);
|
|
264
252
|
} else {
|
|
265
|
-
|
|
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.
|
|
18
|
-
import { createWatcher, injectSSE } from "./watch.
|
|
19
|
-
import { handleResolve, handleServerFunction } from "./resolve.
|
|
20
|
-
import { handleStudioApi } from "./studio-api.
|
|
21
|
-
import { handleCodeApi } from "./code-api.
|
|
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
|
-
|
|
57
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
281
|
+
} catch (e) {
|
|
274
282
|
console.error("Bundle failed for", resolved, e);
|
|
275
283
|
}
|
|
276
284
|
}
|