@jxsuite/server 0.21.4 → 0.22.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.21.4",
3
+ "version": "0.22.0",
4
4
  "description": "Jx development server with live reload, server-side proxy, and studio integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,11 +25,11 @@
25
25
  "test:coverage": "bun test --isolate --coverage"
26
26
  },
27
27
  "dependencies": {
28
- "@anthropic-ai/claude-agent-sdk": "^0.3.153",
29
- "@anthropic-ai/sdk": "^0.99.0",
30
- "@jxsuite/compiler": "^0.21.4",
28
+ "@anthropic-ai/claude-agent-sdk": "^0.3.156",
29
+ "@anthropic-ai/sdk": "^0.100.1",
30
+ "@jxsuite/compiler": "^0.22.0",
31
31
  "@jxsuite/create": "^0.19.0",
32
- "@jxsuite/parser": "^0.21.4",
32
+ "@jxsuite/parser": "^0.22.0",
33
33
  "@modelcontextprotocol/sdk": "^1.29.0",
34
34
  "chokidar": "^5.0.0",
35
35
  "zod": "^4.4.3"
package/src/resolve.js CHANGED
@@ -1,15 +1,42 @@
1
1
  /** Resolve.js — Generic $src module proxy + timing: "server" function proxy */
2
2
 
3
3
  import { resolve, relative, dirname } from "node:path";
4
- import { readFileSync } from "node:fs";
4
+ import { readFileSync, existsSync } from "node:fs";
5
+ import { createRequire } from "node:module";
6
+ import { loadContentTypes } from "@jxsuite/compiler/content-loader";
7
+
8
+ /**
9
+ * Lazy-load project context (project.json + content types) for class instantiation.
10
+ *
11
+ * @param {string} projectRoot
12
+ * @returns {Promise<{
13
+ * config: Record<string, unknown>;
14
+ * contentTypes: Map<string, unknown[]>;
15
+ * root: string;
16
+ * } | null>}
17
+ */
18
+ async function loadProjectContext(projectRoot) {
19
+ const projectJsonPath = resolve(projectRoot, "project.json");
20
+ if (!existsSync(projectJsonPath)) return null;
21
+ try {
22
+ const config = JSON.parse(readFileSync(projectJsonPath, "utf8"));
23
+ const contentTypes = config.contentTypes
24
+ ? await loadContentTypes(projectRoot, config)
25
+ : new Map();
26
+ return { config, contentTypes, root: projectRoot };
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
5
31
 
6
32
  /**
7
33
  * Handle POST /**jx_resolve** — proxy $prototype + $src entries.
8
34
  *
9
35
  * @param {Request} req
10
36
  * @param {string} root
37
+ * @param {string | null} [activeProjectRoot]
11
38
  */
12
- export async function handleResolve(req, root) {
39
+ export async function handleResolve(req, root, activeProjectRoot = null) {
13
40
  let body;
14
41
  try {
15
42
  body = await req.json();
@@ -22,12 +49,25 @@ export async function handleResolve(req, root) {
22
49
 
23
50
  let moduleAbsPath;
24
51
  try {
25
- if ($base) {
26
- const docUrlPath = new URL($base).pathname;
27
- const docDir = docUrlPath.slice(0, docUrlPath.lastIndexOf("/") + 1);
28
- moduleAbsPath = resolve(resolve(root, "." + docDir), $src);
52
+ if ($src.startsWith("./") || $src.startsWith("../")) {
53
+ // Relative path
54
+ if ($base) {
55
+ const docUrlPath = new URL($base).pathname;
56
+ const docDir = docUrlPath.slice(0, docUrlPath.lastIndexOf("/") + 1);
57
+ moduleAbsPath = resolve(resolve(root, "." + docDir), $src);
58
+ } else {
59
+ moduleAbsPath = resolve(activeProjectRoot || root, $src);
60
+ }
29
61
  } else {
30
- moduleAbsPath = resolve(root, $src);
62
+ // npm/bare specifier — use createRequire from project root, fall back to server package
63
+ const projectRoot = activeProjectRoot || root;
64
+ const projRequire = createRequire(resolve(projectRoot, "package.json"));
65
+ try {
66
+ moduleAbsPath = projRequire.resolve($src);
67
+ } catch {
68
+ const serverRequire = createRequire(import.meta.url);
69
+ moduleAbsPath = serverRequire.resolve($src);
70
+ }
31
71
  }
32
72
  } catch (/** @type {unknown} */ e) {
33
73
  return new Response(
@@ -54,6 +94,14 @@ export async function handleResolve(req, root) {
54
94
  const content = readFileSync(moduleAbsPath, "utf8");
55
95
  const classDef = JSON.parse(content);
56
96
 
97
+ // Inject project context for classes that need it
98
+ const projectRoot = activeProjectRoot || root;
99
+ const projectCtx = await loadProjectContext(projectRoot);
100
+ if (projectCtx) {
101
+ config._project = projectCtx;
102
+ }
103
+ config._document = { route: { _pathParams: {} }, state: {} };
104
+
57
105
  if (classDef.$implementation) {
58
106
  // Hybrid mode: redirect to the JS implementation
59
107
  const implPath = resolve(dirname(moduleAbsPath), classDef.$implementation);
package/src/server.js CHANGED
@@ -196,7 +196,7 @@ export async function createDevServer(options) {
196
196
 
197
197
  // $prototype + $src proxy
198
198
  if (path === "/__jx_resolve__" && req.method === "POST") {
199
- return handleResolve(req, absRoot);
199
+ return handleResolve(req, absRoot, activeProjectRoot);
200
200
  }
201
201
 
202
202
  // timing: "server" function proxy
package/src/studio-api.js CHANGED
@@ -11,7 +11,6 @@ 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 { loadContentTypes, queryContentType, findEntry } from "@jxsuite/compiler/content-loader";
15
14
  import * as claude from "./claude-session.js";
16
15
 
17
16
  /** Normalise a path to forward slashes (Windows `path` module returns backslashes). */
@@ -800,12 +799,29 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
800
799
 
801
800
  let moduleAbsPath;
802
801
  try {
803
- if (base) {
804
- const docUrlPath = new URL(base).pathname;
805
- const docDir = docUrlPath.slice(0, docUrlPath.lastIndexOf("/") + 1);
806
- moduleAbsPath = resolve(resolve(root, "." + docDir), src);
802
+ if (src.startsWith("./") || src.startsWith("../")) {
803
+ // Relative path
804
+ if (base) {
805
+ const docUrlPath = new URL(base).pathname;
806
+ const docDir = docUrlPath.slice(0, docUrlPath.lastIndexOf("/") + 1);
807
+ moduleAbsPath = resolve(resolve(root, "." + docDir), src);
808
+ } else {
809
+ moduleAbsPath = resolve(activeProjectRoot || root, src);
810
+ if (!existsSync(moduleAbsPath) && activeProjectRoot) {
811
+ moduleAbsPath = resolve(root, src);
812
+ }
813
+ }
807
814
  } else {
808
- moduleAbsPath = resolve(root, src);
815
+ // npm/bare specifier — use createRequire from project root, fall back to server package
816
+ const projectRoot = activeProjectRoot || root;
817
+ const { createRequire } = await import("node:module");
818
+ const projRequire = createRequire(resolve(projectRoot, "package.json"));
819
+ try {
820
+ moduleAbsPath = projRequire.resolve(src);
821
+ } catch {
822
+ const serverRequire = createRequire(import.meta.url);
823
+ moduleAbsPath = serverRequire.resolve(src);
824
+ }
809
825
  }
810
826
  } catch (/** @type {unknown} */ e) {
811
827
  return Response.json({
@@ -1067,50 +1083,6 @@ export async function handleStudioApi(req, url, root, activeProjectRoot = null)
1067
1083
  }
1068
1084
  }
1069
1085
 
1070
- // ─── Content Type Resolution ──────────────────────────────────────────────
1071
-
1072
- if (path === "/__studio/content" && req.method === "POST") {
1073
- try {
1074
- const body = await req.json();
1075
- const { $prototype, contentType, id, filter, sort, limit } = body;
1076
- const projectRoot = activeProjectRoot || root;
1077
-
1078
- let projectConfig;
1079
- try {
1080
- projectConfig = JSON.parse(readFileSync(resolve(projectRoot, "project.json"), "utf8"));
1081
- } catch {
1082
- return Response.json({ error: "No project.json found" }, { status: 404 });
1083
- }
1084
-
1085
- if (!projectConfig.contentTypes?.[contentType]) {
1086
- return Response.json({ error: `Content type "${contentType}" not found` }, { status: 404 });
1087
- }
1088
-
1089
- const contentTypes = await loadContentTypes(projectRoot, projectConfig);
1090
- const entries = contentTypes.get(contentType);
1091
- if (!entries || entries.length === 0) {
1092
- return Response.json($prototype === "ContentCollection" ? [] : null);
1093
- }
1094
-
1095
- if ($prototype === "ContentCollection") {
1096
- const result = queryContentType(entries, { filter, sort, limit });
1097
- return Response.json(result);
1098
- }
1099
-
1100
- if ($prototype === "ContentEntry") {
1101
- const entry = findEntry(entries, id);
1102
- return Response.json(entry ?? null);
1103
- }
1104
-
1105
- return Response.json({ error: "Invalid $prototype" }, { status: 400 });
1106
- } catch (/** @type {unknown} */ e) {
1107
- return Response.json(
1108
- { error: /** @type {{ message?: string }} */ (e).message },
1109
- { status: 500 },
1110
- );
1111
- }
1112
- }
1113
-
1114
1086
  // ─── AI Assistant ─────────────────────────────────────────────────────────
1115
1087
 
1116
1088
  if (path === "/__studio/ai/auth-status" && req.method === "GET") {