@jxsuite/server 4.4.4 → 4.5.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 -9
- package/src/dev-vars.ts +15 -1
- package/src/jx-mounts.ts +4 -7
- package/src/resolve.ts +56 -16
- package/src/server.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Jx development server with live reload, server-side proxy, and studio integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,22 +44,22 @@
|
|
|
44
44
|
"@jxsuite/ai": "^0.39.3",
|
|
45
45
|
"@jxsuite/catalog": "^0.1.0",
|
|
46
46
|
"@jxsuite/collab": "^0.11.1",
|
|
47
|
-
"@jxsuite/compiler": "^5.1.
|
|
48
|
-
"@jxsuite/create": "^1.3.
|
|
49
|
-
"@jxsuite/import": "^0.40.
|
|
47
|
+
"@jxsuite/compiler": "^5.1.2",
|
|
48
|
+
"@jxsuite/create": "^1.3.14",
|
|
49
|
+
"@jxsuite/import": "^0.40.9",
|
|
50
50
|
"@jxsuite/protocol": "^2.6.1",
|
|
51
|
-
"@jxsuite/runtime": "^4.0
|
|
51
|
+
"@jxsuite/runtime": "^4.1.0",
|
|
52
52
|
"@jxsuite/schema": "^3.0.0",
|
|
53
|
-
"@jxsuite/site": "^2.0.
|
|
54
|
-
"@jxsuite/starters": "^2.0.
|
|
53
|
+
"@jxsuite/site": "^2.0.4",
|
|
54
|
+
"@jxsuite/starters": "^2.0.1",
|
|
55
55
|
"chokidar": "^5.0.0",
|
|
56
56
|
"kysely": "^0.29.6",
|
|
57
|
-
"oxfmt": "^0.
|
|
57
|
+
"oxfmt": "^0.72.0",
|
|
58
58
|
"zod": "^4.6.5"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@jxsuite/auth": "^0.6.2",
|
|
62
62
|
"@jxsuite/connector": "^0.6.2",
|
|
63
|
-
"@jxsuite/parser": "^2.0.
|
|
63
|
+
"@jxsuite/parser": "^2.0.1"
|
|
64
64
|
}
|
|
65
65
|
}
|
package/src/dev-vars.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Dev-vars — `<project>/.dev.vars` parsing (wrangler convention, specs/extensions.md §13).
|
|
3
3
|
*
|
|
4
4
|
* Secret VALUES never enter project.json; locally they live in the git-ignored .dev.vars file and
|
|
5
|
-
* the dev server merges them over process.env when constructing
|
|
5
|
+
* the dev server merges them over process.env when constructing the `env` it hands project code:
|
|
6
|
+
* the extension mounts' and the server-function proxy's (`projectDevEnv`). The format is
|
|
6
7
|
* dotenv-shaped: KEY=VALUE lines, `#` comments, optional single/double quotes. The compiler CLI
|
|
7
8
|
* keeps its own tiny copy (db-push.ts) — the compiler cannot depend on this package.
|
|
8
9
|
*/
|
|
@@ -59,3 +60,16 @@ export function loadDevVars(projectRoot: string): Record<string, string> {
|
|
|
59
60
|
return {};
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The `env` a development host hands project code that runs server-side: `process.env` merged under
|
|
66
|
+
* the project's `.dev.vars`, plus `JX_PROJECT_ROOT`. The extension mounts (server.md §3) and the
|
|
67
|
+
* server-function proxy (server.md §3.3) both build theirs here, so a server function reads the
|
|
68
|
+
* same secrets a mount does.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} root - Absolute project root
|
|
71
|
+
* @returns {Record<string, string | undefined>}
|
|
72
|
+
*/
|
|
73
|
+
export function projectDevEnv(root: string): Record<string, string | undefined> {
|
|
74
|
+
return { ...process.env, ...loadDevVars(root), JX_PROJECT_ROOT: root };
|
|
75
|
+
}
|
package/src/jx-mounts.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Dev conveniences over the production worker:
|
|
9
9
|
*
|
|
10
|
-
* - `env` is process.env merged under the project's `.dev.vars` plus `JX_PROJECT_ROOT
|
|
10
|
+
* - `env` is process.env merged under the project's `.dev.vars` plus `JX_PROJECT_ROOT`
|
|
11
|
+
* (`projectDevEnv`, which the server-function proxy shares);
|
|
11
12
|
* - Connector classes with `local: "<provider>"` are stood in by the registry's class for that
|
|
12
13
|
* provider (e.g. D1 → sqlite at `<project>/.jx/data/<connection>.sqlite`);
|
|
13
14
|
* - Mount options set `autoSync: true`, so table schemas sync additively on first touch.
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
19
20
|
import { resolve } from "node:path";
|
|
20
21
|
import { buildProjectExtensionRegistry } from "@jxsuite/compiler/format-host";
|
|
21
|
-
import {
|
|
22
|
+
import { projectDevEnv } from "./dev-vars.ts";
|
|
22
23
|
import type { ExtensionRegistry } from "@jxsuite/schema/extension-registry";
|
|
23
24
|
import type { FormatEntry } from "@jxsuite/schema/format-registry";
|
|
24
25
|
import type { ProjectConfig } from "@jxsuite/schema/types";
|
|
@@ -121,11 +122,7 @@ async function buildRuntime(
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
const connectors = await resolveConnectorStandins(registry);
|
|
124
|
-
const env: Record<string, unknown> =
|
|
125
|
-
...process.env,
|
|
126
|
-
...loadDevVars(projectRoot),
|
|
127
|
-
JX_PROJECT_ROOT: projectRoot,
|
|
128
|
-
};
|
|
125
|
+
const env: Record<string, unknown> = projectDevEnv(projectRoot);
|
|
129
126
|
|
|
130
127
|
// One shared mutable context, passed to every mount in order (specs/extensions.md §11).
|
|
131
128
|
const ctx: Record<string, unknown> = {};
|
package/src/resolve.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** Resolve.js — Generic $src module proxy + timing: "server" function proxy */
|
|
2
2
|
|
|
3
3
|
import { siteBasePath } from "@jxsuite/schema/asset-paths";
|
|
4
|
-
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
5
|
import { errorMessage, parseClassDef } from "@jxsuite/schema/parse";
|
|
6
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
|
+
import { projectDevEnv } from "./dev-vars.ts";
|
|
8
9
|
import { containedPath } from "./net-guard.ts";
|
|
9
10
|
import { buildProjectExtensionRegistry } from "@jxsuite/compiler/format-host";
|
|
10
11
|
import { loadProjectSections } from "@jxsuite/compiler/project-sections";
|
|
@@ -56,6 +57,41 @@ function isImportable(p: string, root: string, activeProjectRoot: string | null)
|
|
|
56
57
|
return false;
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The directory holding the document a `$base` names, which a relative `$src` resolves against.
|
|
62
|
+
*
|
|
63
|
+
* Hosts spell a base two ways. A page served at a site URL (`jx dev`, the live preview) names a
|
|
64
|
+
* path under `root`, and a single leading slash always means that, even when the path would also
|
|
65
|
+
* read as an absolute one: a root `/app` may hold a site directory `app/`. Studio's canvas
|
|
66
|
+
* addresses a project by its absolute filesystem path instead (`documentBase` in
|
|
67
|
+
* `packages/studio`), so its base arrives as `//abs/project/pages/x.json`, or
|
|
68
|
+
* `/C:/project/pages/x.json` on Windows: the spelling the static file lanes map back to the file
|
|
69
|
+
* (`serveProjectFile`). A `file:` URL's path is a filesystem path too. Those spellings name the
|
|
70
|
+
* path itself only when it lies under `root` or the active project, so any other path keeps meaning
|
|
71
|
+
* a site URL. The pathname is percent-decoded first, because a URL encodes a space in a directory
|
|
72
|
+
* name and the filesystem does not.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} $base - The document URL the runtime sent
|
|
75
|
+
* @param {string} root - The root a site URL names a path under
|
|
76
|
+
* @param {string} projectRoot - The active project, which may sit outside `root`
|
|
77
|
+
* @returns {string} An absolute directory
|
|
78
|
+
*/
|
|
79
|
+
function baseDirectory($base: string, root: string, projectRoot: string): string {
|
|
80
|
+
const url = new URL($base);
|
|
81
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
82
|
+
const urlDir = pathname.slice(0, pathname.lastIndexOf("/") + 1);
|
|
83
|
+
const spellsPath = urlDir.startsWith("//") || /^\/[A-Za-z]:/.test(urlDir);
|
|
84
|
+
const fsDir = spellsPath ? urlDir.slice(1) : url.protocol === "file:" ? urlDir : null;
|
|
85
|
+
if (
|
|
86
|
+
fsDir !== null &&
|
|
87
|
+
isAbsolute(fsDir) &&
|
|
88
|
+
(containedPath(fsDir, root) !== null || containedPath(fsDir, projectRoot) !== null)
|
|
89
|
+
) {
|
|
90
|
+
return resolve(fsDir);
|
|
91
|
+
}
|
|
92
|
+
return resolve(root, `.${urlDir}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
59
95
|
/** Per-project context cache, invalidated when project.json changes on disk. */
|
|
60
96
|
const projectContextCache = new Map<
|
|
61
97
|
string,
|
|
@@ -267,13 +303,20 @@ export async function handleResolve(
|
|
|
267
303
|
}
|
|
268
304
|
|
|
269
305
|
/**
|
|
270
|
-
* Handle POST
|
|
271
|
-
*
|
|
306
|
+
* Handle POST `/__jx_server__` — proxy timing: "server" function calls. It is the only way an
|
|
307
|
+
* interpreting runtime calls a server function (spec.md §11.4): the runtime never imports the
|
|
308
|
+
* module in the browser, it posts the call here, and the function runs server-side with `env`.
|
|
309
|
+
*
|
|
310
|
+
* A relative `$src` resolves against the directory of the document `$base` names (`baseDirectory`):
|
|
311
|
+
* a site URL's path under `root`, or the absolute path Studio's canvas sends. The imported module
|
|
312
|
+
* stays cached for as long as the host runs, so an edit to it takes effect when the host restarts.
|
|
272
313
|
*
|
|
273
314
|
* @param {Request} req
|
|
274
|
-
* @param {string} root
|
|
315
|
+
* @param {string} root - The root a site-URL `$base`, or a `$src` without one, resolves under
|
|
316
|
+
* @param {string} [projectRoot] - The active project: the function's `env` carries its `.dev.vars`,
|
|
317
|
+
* and a module under it is importable as one under `root` is. `root` when omitted
|
|
275
318
|
*/
|
|
276
|
-
export async function handleServerFunction(req: Request, root: string) {
|
|
319
|
+
export async function handleServerFunction(req: Request, root: string, projectRoot = root) {
|
|
277
320
|
let body: ServerFunctionBody;
|
|
278
321
|
try {
|
|
279
322
|
body = (await req.json()) as ServerFunctionBody;
|
|
@@ -288,18 +331,14 @@ export async function handleServerFunction(req: Request, root: string) {
|
|
|
288
331
|
|
|
289
332
|
let moduleAbsPath;
|
|
290
333
|
try {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
moduleAbsPath = resolve(resolve(root, `.${docDir}`), $src);
|
|
295
|
-
} else {
|
|
296
|
-
moduleAbsPath = resolve(root, $src);
|
|
297
|
-
}
|
|
334
|
+
moduleAbsPath = $base
|
|
335
|
+
? resolve(baseDirectory($base, root, projectRoot), $src)
|
|
336
|
+
: resolve(root, $src);
|
|
298
337
|
} catch (error) {
|
|
299
338
|
return problem("invalidRequest", `Cannot resolve $src: ${errorMessage(error)}`);
|
|
300
339
|
}
|
|
301
340
|
|
|
302
|
-
if (!isImportable(moduleAbsPath, root,
|
|
341
|
+
if (!isImportable(moduleAbsPath, root, projectRoot)) {
|
|
303
342
|
return new Response(`$src "${$src}" escapes the project root`, { status: 403 });
|
|
304
343
|
}
|
|
305
344
|
|
|
@@ -320,11 +359,12 @@ export async function handleServerFunction(req: Request, root: string) {
|
|
|
320
359
|
}
|
|
321
360
|
|
|
322
361
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
362
|
+
/* Match the production contract `fn(args, env)` (compiler emits `fn(args, c.env)`): the dev
|
|
363
|
+
proxy runs server-side, so its `env` is the extension mounts' own, `process.env` merged under
|
|
364
|
+
the project's `.dev.vars` plus `JX_PROJECT_ROOT` (server.md §3.3). */
|
|
325
365
|
const result = await (fn as (args: Record<string, unknown>, env: unknown) => unknown)(
|
|
326
366
|
args,
|
|
327
|
-
|
|
367
|
+
projectDevEnv(projectRoot),
|
|
328
368
|
);
|
|
329
369
|
return Response.json(result ?? null);
|
|
330
370
|
} catch (error) {
|
package/src/server.ts
CHANGED
|
@@ -359,9 +359,11 @@ export async function createDevServer(options: {
|
|
|
359
359
|
return handleResolve(req, absRoot, activeProjectRoot);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
|
|
362
|
+
/* Timing: "server" function proxy. Its `env` carries the active project's `.dev.vars`, as the
|
|
363
|
+
extension mounts' does below, and a module under that project imports even when it sits
|
|
364
|
+
outside absRoot, as the static lanes below serve it. */
|
|
363
365
|
if (path === "/__jx_server__" && req.method === "POST") {
|
|
364
|
-
return handleServerFunction(req, absRoot);
|
|
366
|
+
return handleServerFunction(req, absRoot, activeProjectRoot ?? absRoot);
|
|
365
367
|
}
|
|
366
368
|
|
|
367
369
|
// Extension server mounts (/_jx/data etc.) — registry-driven, same wire contract as the
|