@forgeax/engine-remote 0.1.3 → 0.1.6
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/README.md +3 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/cli-defaults.unit.test.d.ts +2 -0
- package/dist/__tests__/cli-defaults.unit.test.d.ts.map +1 -0
- package/dist/__tests__/simulation-inspect.integration.test.d.ts +2 -0
- package/dist/__tests__/simulation-inspect.integration.test.d.ts.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.mjs +336 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/defineSubcommand.d.ts +40 -0
- package/dist/defineSubcommand.d.ts.map +1 -0
- package/dist/execute.d.ts +1 -0
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.mjs +2 -0
- package/dist/execute.mjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/introspect.d.ts +2 -0
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.mjs +5 -8
- package/dist/introspect.mjs.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.mjs +20 -58
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -3
- package/src/__tests__/cli-defaults.unit.test.ts +69 -0
- package/src/__tests__/console.unit.test.ts +676 -9
- package/src/__tests__/errors.unit.test.ts +3 -17
- package/src/__tests__/execute.async.test.ts +17 -13
- package/src/__tests__/server.unit.test.ts +19 -262
- package/src/__tests__/simulation-inspect.integration.test.ts +67 -0
- package/src/__tests__/vm-async-eval-verify.mjs +1 -1
- package/src/cli.ts +333 -0
- package/src/defineSubcommand.ts +169 -0
- package/src/execute.ts +4 -1
- package/src/index.ts +5 -4
- package/src/introspect.ts +10 -15
- package/src/server.ts +14 -60
- package/dist/__tests__/asset-runtime-inspection.unit.test.d.ts +0 -2
- package/dist/__tests__/asset-runtime-inspection.unit.test.d.ts.map +0 -1
- package/src/__tests__/asset-runtime-inspection.unit.test.ts +0 -16
package/src/server.ts
CHANGED
|
@@ -46,8 +46,8 @@ export type StartServerOptions = {
|
|
|
46
46
|
readonly profiler?: unknown;
|
|
47
47
|
/** Structural report provider; remote never imports the App owner. */
|
|
48
48
|
readonly execution?: unknown;
|
|
49
|
-
/**
|
|
50
|
-
readonly
|
|
49
|
+
/** Read-only World-owned simulation inspection root. */
|
|
50
|
+
readonly simulation?: unknown;
|
|
51
51
|
/**
|
|
52
52
|
* Host-owned RHI capture capability for eval-scope injection (plan-strategy D-4).
|
|
53
53
|
* It is exposed as the single `rhiCapture` eval root.
|
|
@@ -75,13 +75,14 @@ type JsonRpcResponse = {
|
|
|
75
75
|
} & ({ result: unknown } | { error: JsonRpcError });
|
|
76
76
|
|
|
77
77
|
function inspectorErrorToJsonRpc(e: RemoteError): JsonRpcError {
|
|
78
|
+
const detail = (e as unknown as { detail?: unknown }).detail;
|
|
78
79
|
const data: Record<string, unknown> = {
|
|
79
80
|
code: e.code,
|
|
80
81
|
expected: e.expected,
|
|
81
82
|
hint: e.hint,
|
|
82
83
|
message: e.message,
|
|
83
84
|
};
|
|
84
|
-
if (
|
|
85
|
+
if (detail !== undefined) data.detail = detail;
|
|
85
86
|
return {
|
|
86
87
|
code: REMOTE_TO_JSONRPC[e.code],
|
|
87
88
|
message: REMOTE_ERROR_MESSAGES[e.code],
|
|
@@ -106,47 +107,6 @@ function respondOk(id: number | string | null, result: unknown): JsonRpcResponse
|
|
|
106
107
|
return { jsonrpc: '2.0', id, result };
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
type EvalResultShape = 'bigint' | 'cyclic-object' | 'unsupported';
|
|
110
|
-
|
|
111
|
-
const EVAL_RESULT_NOT_SERIALIZABLE_EXPECTED = 'eval result is JSON-serializable';
|
|
112
|
-
const EVAL_RESULT_NOT_SERIALIZABLE_HINT =
|
|
113
|
-
'return a JSON-safe value; BigInt and cyclic objects are unsupported over JSON-RPC';
|
|
114
|
-
|
|
115
|
-
function classifyEvalResultShape(value: unknown): EvalResultShape {
|
|
116
|
-
if (typeof value === 'bigint') return 'bigint';
|
|
117
|
-
if (value !== null && typeof value === 'object') return 'cyclic-object';
|
|
118
|
-
return 'unsupported';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function serializeResponse(response: JsonRpcResponse): string {
|
|
122
|
-
try {
|
|
123
|
-
const serialized = JSON.stringify(response);
|
|
124
|
-
if (serialized !== undefined) return serialized;
|
|
125
|
-
} catch {
|
|
126
|
-
// A successful eval result can be a valid JavaScript value that JSON-RPC
|
|
127
|
-
// cannot carry. Convert that transport-only failure below.
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (!('result' in response)) {
|
|
131
|
-
return JSON.stringify(respondError(response.id, -32603, 'Internal error')) as string;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const error = new RemoteError({
|
|
135
|
-
code: 'eval-result-not-serializable',
|
|
136
|
-
expected: EVAL_RESULT_NOT_SERIALIZABLE_EXPECTED,
|
|
137
|
-
hint: EVAL_RESULT_NOT_SERIALIZABLE_HINT,
|
|
138
|
-
detail: {
|
|
139
|
-
code: 'eval-result-not-serializable',
|
|
140
|
-
shape: classifyEvalResultShape(response.result),
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
return JSON.stringify({
|
|
144
|
-
jsonrpc: '2.0',
|
|
145
|
-
id: response.id,
|
|
146
|
-
error: inspectorErrorToJsonRpc(error),
|
|
147
|
-
}) as string;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
110
|
function isValidId(v: unknown): v is number | string | null {
|
|
151
111
|
return typeof v === 'number' || typeof v === 'string' || v === null;
|
|
152
112
|
}
|
|
@@ -161,20 +121,17 @@ async function handleEnvelope(
|
|
|
161
121
|
introspection: readonly ComponentIntrospectionDescriptor[];
|
|
162
122
|
profiler: unknown | undefined;
|
|
163
123
|
execution: unknown | undefined;
|
|
164
|
-
|
|
124
|
+
simulation: unknown | undefined;
|
|
165
125
|
host: string;
|
|
166
126
|
port: number;
|
|
167
127
|
},
|
|
168
128
|
): Promise<JsonRpcResponse | null> {
|
|
169
|
-
let parsed: JsonRpcRequest
|
|
129
|
+
let parsed: JsonRpcRequest;
|
|
170
130
|
try {
|
|
171
131
|
parsed = JSON.parse(raw) as JsonRpcRequest;
|
|
172
132
|
} catch {
|
|
173
133
|
return respondError(null, -32700, 'Parse error');
|
|
174
134
|
}
|
|
175
|
-
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
176
|
-
return respondError(null, -32600, 'Invalid Request');
|
|
177
|
-
}
|
|
178
135
|
if (typeof parsed.method !== 'string') {
|
|
179
136
|
const id = isValidId(parsed.id) ? parsed.id : null;
|
|
180
137
|
return respondError(id, -32600, 'Invalid Request');
|
|
@@ -193,6 +150,7 @@ async function handleEnvelope(
|
|
|
193
150
|
...(ctx.rhiCapture !== undefined ? { rhiCapture: ctx.rhiCapture } : {}),
|
|
194
151
|
...(ctx.profiler !== undefined ? { profiler: ctx.profiler } : {}),
|
|
195
152
|
...(ctx.execution !== undefined ? { execution: ctx.execution } : {}),
|
|
153
|
+
...(ctx.simulation !== undefined ? { simulation: ctx.simulation } : {}),
|
|
196
154
|
...(ctx.introspection.length > 0 ? { introspection: ctx.introspection } : {}),
|
|
197
155
|
}),
|
|
198
156
|
);
|
|
@@ -209,7 +167,7 @@ async function handleEnvelope(
|
|
|
209
167
|
rhiCapture: ctx.rhiCapture,
|
|
210
168
|
profiler: ctx.profiler,
|
|
211
169
|
execution: ctx.execution,
|
|
212
|
-
|
|
170
|
+
simulation: ctx.simulation,
|
|
213
171
|
});
|
|
214
172
|
if (result.ok) {
|
|
215
173
|
response = respondOk(id, result.value);
|
|
@@ -250,7 +208,7 @@ export function startServer(opts: StartServerOptions): Promise<Result<ConsoleHan
|
|
|
250
208
|
const introspection = opts.introspection ?? [];
|
|
251
209
|
const profiler = isProfilerRoot(opts.profiler) ? opts.profiler : undefined;
|
|
252
210
|
const execution = isExecutionRoot(opts.execution) ? opts.execution : undefined;
|
|
253
|
-
const
|
|
211
|
+
const simulation = opts.simulation;
|
|
254
212
|
let settled = false;
|
|
255
213
|
let boundPort = opts.port;
|
|
256
214
|
|
|
@@ -286,13 +244,13 @@ export function startServer(opts: StartServerOptions): Promise<Result<ConsoleHan
|
|
|
286
244
|
introspection,
|
|
287
245
|
profiler,
|
|
288
246
|
execution,
|
|
289
|
-
|
|
247
|
+
simulation,
|
|
290
248
|
host,
|
|
291
249
|
port: boundPort,
|
|
292
250
|
})
|
|
293
251
|
.then((response) => {
|
|
294
252
|
if (response !== null && ws.readyState === ws.OPEN) {
|
|
295
|
-
ws.send(
|
|
253
|
+
ws.send(JSON.stringify(response));
|
|
296
254
|
}
|
|
297
255
|
})
|
|
298
256
|
.catch((e: unknown) => {
|
|
@@ -310,20 +268,16 @@ export function startServer(opts: StartServerOptions): Promise<Result<ConsoleHan
|
|
|
310
268
|
settled = true;
|
|
311
269
|
const address = wss.address();
|
|
312
270
|
const port = typeof address === 'object' && address !== null ? address.port : opts.port;
|
|
313
|
-
let closePromise: Promise<void> | undefined;
|
|
314
271
|
boundPort = port;
|
|
315
272
|
const handle: ConsoleHandle = {
|
|
316
273
|
port,
|
|
317
|
-
close: () =>
|
|
318
|
-
|
|
319
|
-
closePromise = new Promise<void>((closeResolve) => {
|
|
274
|
+
close: () =>
|
|
275
|
+
new Promise<void>((closeResolve) => {
|
|
320
276
|
for (const client of wss.clients) {
|
|
321
277
|
client.terminate();
|
|
322
278
|
}
|
|
323
279
|
wss.close(() => closeResolve());
|
|
324
|
-
})
|
|
325
|
-
return closePromise;
|
|
326
|
-
},
|
|
280
|
+
}),
|
|
327
281
|
};
|
|
328
282
|
resolve(ok(handle));
|
|
329
283
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asset-runtime-inspection.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/asset-runtime-inspection.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { buildIntrospectDoc } from '../introspect';
|
|
3
|
-
|
|
4
|
-
describe('remote asset runtime inspection', () => {
|
|
5
|
-
it('projects the bounded registry snapshot operations without owning registry state', () => {
|
|
6
|
-
const doc = buildIntrospectDoc('127.0.0.1', 5732, {
|
|
7
|
-
world: {},
|
|
8
|
-
renderer: {},
|
|
9
|
-
assets: { snapshot: () => ({ ready: [], pending: 0 }) },
|
|
10
|
-
}) as { roots: Record<string, { operations?: Record<string, string> }> };
|
|
11
|
-
|
|
12
|
-
expect(doc.roots.assets).toMatchObject({
|
|
13
|
-
operations: { load: 'assets.load(guid, expectedKind)', snapshot: 'assets.snapshot()' },
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
});
|