@jxrstudios/jxr 1.0.9 → 1.0.11
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/bin/jxr.js +6 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -2
- package/dist/jxr-server-manager.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/jxr-server-manager.ts +57 -1
- package/zzz_react_template/App.tsx +43 -156
- package/zzz_react_template/components/ErrorBoundary.tsx +62 -0
- package/zzz_react_template/components/ManusDialog.tsx +85 -0
- package/zzz_react_template/components/Map.tsx +155 -0
- package/zzz_react_template/components/jxr/CodeEditor.tsx +313 -0
- package/zzz_react_template/components/jxr/FileExplorer.tsx +230 -0
- package/zzz_react_template/components/jxr/IDEShell.tsx +159 -0
- package/zzz_react_template/components/jxr/LandingPage.tsx +414 -0
- package/zzz_react_template/components/jxr/LivePreview.tsx +169 -0
- package/zzz_react_template/components/jxr/PerformanceDashboard.tsx +379 -0
- package/zzz_react_template/components/jxr/TopBar.tsx +149 -0
- package/zzz_react_template/components/ui/accordion.tsx +64 -0
- package/zzz_react_template/components/ui/alert-dialog.tsx +155 -0
- package/zzz_react_template/components/ui/alert.tsx +66 -0
- package/zzz_react_template/components/ui/aspect-ratio.tsx +9 -0
- package/zzz_react_template/components/ui/avatar.tsx +51 -0
- package/zzz_react_template/components/ui/badge.tsx +46 -0
- package/zzz_react_template/components/ui/breadcrumb.tsx +109 -0
- package/zzz_react_template/components/ui/button-group.tsx +83 -0
- package/zzz_react_template/components/ui/button.tsx +60 -0
- package/zzz_react_template/components/ui/calendar.tsx +211 -0
- package/zzz_react_template/components/ui/card.tsx +92 -0
- package/zzz_react_template/components/ui/carousel.tsx +239 -0
- package/zzz_react_template/components/ui/chart.tsx +355 -0
- package/zzz_react_template/components/ui/checkbox.tsx +30 -0
- package/zzz_react_template/components/ui/collapsible.tsx +31 -0
- package/zzz_react_template/components/ui/command.tsx +184 -0
- package/zzz_react_template/components/ui/context-menu.tsx +250 -0
- package/zzz_react_template/components/ui/dialog.tsx +209 -0
- package/zzz_react_template/components/ui/drawer.tsx +133 -0
- package/zzz_react_template/components/ui/dropdown-menu.tsx +255 -0
- package/zzz_react_template/components/ui/empty.tsx +104 -0
- package/zzz_react_template/components/ui/field.tsx +242 -0
- package/zzz_react_template/components/ui/form.tsx +168 -0
- package/zzz_react_template/components/ui/hover-card.tsx +42 -0
- package/zzz_react_template/components/ui/input-group.tsx +168 -0
- package/zzz_react_template/components/ui/input-otp.tsx +75 -0
- package/zzz_react_template/components/ui/input.tsx +70 -0
- package/zzz_react_template/components/ui/item.tsx +193 -0
- package/zzz_react_template/components/ui/kbd.tsx +28 -0
- package/zzz_react_template/components/ui/label.tsx +22 -0
- package/zzz_react_template/components/ui/menubar.tsx +274 -0
- package/zzz_react_template/components/ui/navigation-menu.tsx +168 -0
- package/zzz_react_template/components/ui/pagination.tsx +127 -0
- package/zzz_react_template/components/ui/popover.tsx +46 -0
- package/zzz_react_template/components/ui/progress.tsx +29 -0
- package/zzz_react_template/components/ui/radio-group.tsx +43 -0
- package/zzz_react_template/components/ui/resizable.tsx +54 -0
- package/zzz_react_template/components/ui/scroll-area.tsx +56 -0
- package/zzz_react_template/components/ui/select.tsx +185 -0
- package/zzz_react_template/components/ui/separator.tsx +26 -0
- package/zzz_react_template/components/ui/sheet.tsx +139 -0
- package/zzz_react_template/components/ui/sidebar.tsx +734 -0
- package/zzz_react_template/components/ui/skeleton.tsx +13 -0
- package/zzz_react_template/components/ui/slider.tsx +61 -0
- package/zzz_react_template/components/ui/sonner.tsx +23 -0
- package/zzz_react_template/components/ui/spinner.tsx +16 -0
- package/zzz_react_template/components/ui/switch.tsx +29 -0
- package/zzz_react_template/components/ui/table.tsx +114 -0
- package/zzz_react_template/components/ui/tabs.tsx +64 -0
- package/zzz_react_template/components/ui/textarea.tsx +67 -0
- package/zzz_react_template/components/ui/toggle-group.tsx +73 -0
- package/zzz_react_template/components/ui/toggle.tsx +45 -0
- package/zzz_react_template/components/ui/tooltip.tsx +59 -0
- package/zzz_react_template/const.ts +17 -0
- package/zzz_react_template/contexts/JXRContext.tsx +264 -0
- package/zzz_react_template/contexts/ThemeContext.tsx +64 -0
- package/zzz_react_template/hooks/useComposition.ts +81 -0
- package/zzz_react_template/hooks/useMobile.tsx +21 -0
- package/zzz_react_template/hooks/usePersistFn.ts +20 -0
- package/zzz_react_template/index.css +518 -11
- package/zzz_react_template/lib/jxr-runtime/index.ts +201 -0
- package/zzz_react_template/lib/jxr-runtime/module-resolver.ts +520 -0
- package/zzz_react_template/lib/jxr-runtime/moq-transport.ts +267 -0
- package/zzz_react_template/lib/jxr-runtime/web-crypto.ts +279 -0
- package/zzz_react_template/lib/jxr-runtime/worker-pool.ts +321 -0
- package/zzz_react_template/lib/utils.ts +6 -0
- package/zzz_react_template/main.tsx +4 -9
- package/zzz_react_template/pages/Docs.tsx +955 -0
- package/zzz_react_template/pages/Home.tsx +1080 -0
- package/zzz_react_template/pages/NotFound.tsx +105 -0
- package/zzz_react_template/tsconfig.json +24 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JXR.js — Worker Pool Engine
|
|
3
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
* Design: LavaFlow OS — Thermal Precision + Edge Command
|
|
5
|
+
* Layer: Core Runtime / Worker Orchestration
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* WorkerPool manages a fleet of Web Workers for parallel task execution.
|
|
9
|
+
* Tasks are dispatched via a priority queue and results streamed back
|
|
10
|
+
* through a structured message protocol. Each worker is isolated with
|
|
11
|
+
* its own module scope, enabling true parallelism without shared state.
|
|
12
|
+
*
|
|
13
|
+
* Performance targets:
|
|
14
|
+
* - Task dispatch latency: <1ms
|
|
15
|
+
* - Worker spawn time: <5ms (pre-warmed pool)
|
|
16
|
+
* - Throughput: saturate all available CPU cores
|
|
17
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type WorkerStatus = 'idle' | 'busy' | 'error' | 'terminated';
|
|
21
|
+
export type TaskPriority = 'critical' | 'high' | 'normal' | 'low';
|
|
22
|
+
|
|
23
|
+
export interface WorkerTask<T = unknown, R = unknown> {
|
|
24
|
+
id: string;
|
|
25
|
+
type: string;
|
|
26
|
+
payload: T;
|
|
27
|
+
priority: TaskPriority;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
resolve: (result: R) => void;
|
|
30
|
+
reject: (error: Error) => void;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WorkerMetrics {
|
|
35
|
+
workerId: string;
|
|
36
|
+
status: WorkerStatus;
|
|
37
|
+
tasksCompleted: number;
|
|
38
|
+
tasksFailed: number;
|
|
39
|
+
avgLatencyMs: number;
|
|
40
|
+
lastActiveAt: number;
|
|
41
|
+
cpuLoad: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PoolMetrics {
|
|
45
|
+
totalWorkers: number;
|
|
46
|
+
idleWorkers: number;
|
|
47
|
+
busyWorkers: number;
|
|
48
|
+
queueDepth: number;
|
|
49
|
+
throughputPerSec: number;
|
|
50
|
+
avgLatencyMs: number;
|
|
51
|
+
totalTasksCompleted: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface WorkerEntry {
|
|
55
|
+
worker: Worker;
|
|
56
|
+
status: WorkerStatus;
|
|
57
|
+
currentTaskId: string | null;
|
|
58
|
+
metrics: WorkerMetrics;
|
|
59
|
+
latencyHistory: number[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const PRIORITY_WEIGHTS: Record<TaskPriority, number> = {
|
|
63
|
+
critical: 4,
|
|
64
|
+
high: 3,
|
|
65
|
+
normal: 2,
|
|
66
|
+
low: 1,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* WorkerPool — Pre-warmed, priority-aware Web Worker fleet
|
|
71
|
+
*/
|
|
72
|
+
export class WorkerPool {
|
|
73
|
+
private workers: Map<string, WorkerEntry> = new Map();
|
|
74
|
+
private taskQueue: WorkerTask[] = [];
|
|
75
|
+
private pendingTasks: Map<string, WorkerTask> = new Map();
|
|
76
|
+
private metricsHistory: number[] = [];
|
|
77
|
+
private throughputWindow: number[] = [];
|
|
78
|
+
private readonly maxWorkers: number;
|
|
79
|
+
private readonly workerScript: string;
|
|
80
|
+
private taskCounter = 0;
|
|
81
|
+
private listeners: Map<string, Set<(metrics: PoolMetrics) => void>> = new Map();
|
|
82
|
+
|
|
83
|
+
constructor(workerScript: string, maxWorkers?: number) {
|
|
84
|
+
this.workerScript = workerScript;
|
|
85
|
+
this.maxWorkers = maxWorkers ?? Math.max(2, (navigator.hardwareConcurrency ?? 4) - 1);
|
|
86
|
+
this.prewarm();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Pre-warm the pool to eliminate cold-start latency */
|
|
90
|
+
private prewarm(): void {
|
|
91
|
+
const initialCount = Math.min(2, this.maxWorkers);
|
|
92
|
+
for (let i = 0; i < initialCount; i++) {
|
|
93
|
+
this.spawnWorker();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private spawnWorker(): string {
|
|
98
|
+
const workerId = `worker-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
99
|
+
const worker = new Worker(this.workerScript, { type: 'module' });
|
|
100
|
+
|
|
101
|
+
const entry: WorkerEntry = {
|
|
102
|
+
worker,
|
|
103
|
+
status: 'idle',
|
|
104
|
+
currentTaskId: null,
|
|
105
|
+
latencyHistory: [],
|
|
106
|
+
metrics: {
|
|
107
|
+
workerId,
|
|
108
|
+
status: 'idle',
|
|
109
|
+
tasksCompleted: 0,
|
|
110
|
+
tasksFailed: 0,
|
|
111
|
+
avgLatencyMs: 0,
|
|
112
|
+
lastActiveAt: Date.now(),
|
|
113
|
+
cpuLoad: 0,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
worker.onmessage = (event) => this.handleWorkerMessage(workerId, event);
|
|
118
|
+
worker.onerror = (error) => this.handleWorkerError(workerId, error);
|
|
119
|
+
|
|
120
|
+
this.workers.set(workerId, entry);
|
|
121
|
+
return workerId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private handleWorkerMessage(workerId: string, event: MessageEvent): void {
|
|
125
|
+
const { taskId, result, error, type } = event.data;
|
|
126
|
+
const entry = this.workers.get(workerId);
|
|
127
|
+
if (!entry) return;
|
|
128
|
+
|
|
129
|
+
if (type === 'metrics') {
|
|
130
|
+
entry.metrics.cpuLoad = event.data.cpuLoad ?? 0;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const task = this.pendingTasks.get(taskId);
|
|
135
|
+
if (!task) return;
|
|
136
|
+
|
|
137
|
+
const latency = Date.now() - task.timestamp;
|
|
138
|
+
entry.latencyHistory.push(latency);
|
|
139
|
+
if (entry.latencyHistory.length > 50) entry.latencyHistory.shift();
|
|
140
|
+
|
|
141
|
+
entry.metrics.avgLatencyMs =
|
|
142
|
+
entry.latencyHistory.reduce((a, b) => a + b, 0) / entry.latencyHistory.length;
|
|
143
|
+
entry.metrics.lastActiveAt = Date.now();
|
|
144
|
+
|
|
145
|
+
if (error) {
|
|
146
|
+
entry.metrics.tasksFailed++;
|
|
147
|
+
task.reject(new Error(error));
|
|
148
|
+
} else {
|
|
149
|
+
entry.metrics.tasksCompleted++;
|
|
150
|
+
this.throughputWindow.push(Date.now());
|
|
151
|
+
task.resolve(result);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.pendingTasks.delete(taskId);
|
|
155
|
+
entry.status = 'idle';
|
|
156
|
+
entry.metrics.status = 'idle';
|
|
157
|
+
entry.currentTaskId = null;
|
|
158
|
+
|
|
159
|
+
this.emitMetrics();
|
|
160
|
+
this.drainQueue();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private handleWorkerError(workerId: string, error: ErrorEvent): void {
|
|
164
|
+
const entry = this.workers.get(workerId);
|
|
165
|
+
if (!entry) return;
|
|
166
|
+
|
|
167
|
+
entry.status = 'error';
|
|
168
|
+
entry.metrics.status = 'error';
|
|
169
|
+
|
|
170
|
+
if (entry.currentTaskId) {
|
|
171
|
+
const task = this.pendingTasks.get(entry.currentTaskId);
|
|
172
|
+
if (task) {
|
|
173
|
+
task.reject(new Error(error.message));
|
|
174
|
+
this.pendingTasks.delete(entry.currentTaskId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Respawn worker
|
|
179
|
+
entry.worker.terminate();
|
|
180
|
+
this.workers.delete(workerId);
|
|
181
|
+
this.spawnWorker();
|
|
182
|
+
this.drainQueue();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private getIdleWorker(): WorkerEntry | null {
|
|
186
|
+
for (const [, entry] of Array.from(this.workers.entries())) {
|
|
187
|
+
if (entry.status === 'idle') return entry;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private drainQueue(): void {
|
|
193
|
+
if (this.taskQueue.length === 0) return;
|
|
194
|
+
|
|
195
|
+
// Sort by priority weight descending
|
|
196
|
+
this.taskQueue.sort(
|
|
197
|
+
(a, b) => PRIORITY_WEIGHTS[b.priority] - PRIORITY_WEIGHTS[a.priority]
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
let idleWorker = this.getIdleWorker();
|
|
201
|
+
|
|
202
|
+
// Scale up if needed and under cap
|
|
203
|
+
if (!idleWorker && this.workers.size < this.maxWorkers) {
|
|
204
|
+
const newId = this.spawnWorker();
|
|
205
|
+
idleWorker = this.workers.get(newId) ?? null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!idleWorker) return;
|
|
209
|
+
|
|
210
|
+
const task = this.taskQueue.shift()!;
|
|
211
|
+
this.dispatchToWorker(idleWorker, task);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private dispatchToWorker(entry: WorkerEntry, task: WorkerTask): void {
|
|
215
|
+
entry.status = 'busy';
|
|
216
|
+
entry.metrics.status = 'busy';
|
|
217
|
+
entry.currentTaskId = task.id;
|
|
218
|
+
this.pendingTasks.set(task.id, task);
|
|
219
|
+
|
|
220
|
+
entry.worker.postMessage({
|
|
221
|
+
taskId: task.id,
|
|
222
|
+
type: task.type,
|
|
223
|
+
payload: task.payload,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (task.timeoutMs) {
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
if (this.pendingTasks.has(task.id)) {
|
|
229
|
+
task.reject(new Error(`Task ${task.id} timed out after ${task.timeoutMs}ms`));
|
|
230
|
+
this.pendingTasks.delete(task.id);
|
|
231
|
+
}
|
|
232
|
+
}, task.timeoutMs);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Submit a task to the pool, returns a Promise */
|
|
237
|
+
submit<T = unknown, R = unknown>(
|
|
238
|
+
type: string,
|
|
239
|
+
payload: T,
|
|
240
|
+
options: { priority?: TaskPriority; timeoutMs?: number } = {}
|
|
241
|
+
): Promise<R> {
|
|
242
|
+
return new Promise<R>((resolve, reject) => {
|
|
243
|
+
const task: WorkerTask<T, R> = {
|
|
244
|
+
id: `task-${++this.taskCounter}-${Date.now()}`,
|
|
245
|
+
type,
|
|
246
|
+
payload,
|
|
247
|
+
priority: options.priority ?? 'normal',
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
timeoutMs: options.timeoutMs,
|
|
250
|
+
resolve,
|
|
251
|
+
reject,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const idleWorker = this.getIdleWorker();
|
|
255
|
+
if (idleWorker) {
|
|
256
|
+
this.dispatchToWorker(idleWorker, task as WorkerTask);
|
|
257
|
+
} else {
|
|
258
|
+
this.taskQueue.push(task as WorkerTask);
|
|
259
|
+
// Scale up if under cap
|
|
260
|
+
if (this.workers.size < this.maxWorkers) {
|
|
261
|
+
this.spawnWorker();
|
|
262
|
+
this.drainQueue();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** Get current pool metrics */
|
|
269
|
+
getMetrics(): PoolMetrics {
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
// Clean throughput window to last 1 second
|
|
272
|
+
this.throughputWindow = this.throughputWindow.filter((t) => now - t < 1000);
|
|
273
|
+
|
|
274
|
+
const allLatencies = Array.from(this.workers.values() as Iterable<WorkerEntry>).flatMap(
|
|
275
|
+
(e) => e.latencyHistory
|
|
276
|
+
);
|
|
277
|
+
const avgLatency =
|
|
278
|
+
allLatencies.length > 0
|
|
279
|
+
? allLatencies.reduce((a, b) => a + b, 0) / allLatencies.length
|
|
280
|
+
: 0;
|
|
281
|
+
|
|
282
|
+
const totalCompleted = Array.from(this.workers.values() as Iterable<WorkerEntry>).reduce(
|
|
283
|
+
(sum, e) => sum + e.metrics.tasksCompleted,
|
|
284
|
+
0
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
totalWorkers: this.workers.size,
|
|
289
|
+
idleWorkers: Array.from(this.workers.values() as Iterable<WorkerEntry>).filter((e) => e.status === 'idle').length,
|
|
290
|
+
busyWorkers: Array.from(this.workers.values() as Iterable<WorkerEntry>).filter((e) => e.status === 'busy').length,
|
|
291
|
+
queueDepth: this.taskQueue.length,
|
|
292
|
+
throughputPerSec: this.throughputWindow.length,
|
|
293
|
+
avgLatencyMs: Math.round(avgLatency * 10) / 10,
|
|
294
|
+
totalTasksCompleted: totalCompleted,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getWorkerMetrics(): WorkerMetrics[] {
|
|
299
|
+
return Array.from(this.workers.values() as Iterable<WorkerEntry>).map((e) => ({ ...e.metrics }));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
onMetrics(event: string, cb: (metrics: PoolMetrics) => void): () => void {
|
|
303
|
+
if (!this.listeners.has(event)) this.listeners.set(event, new Set());
|
|
304
|
+
this.listeners.get(event)!.add(cb);
|
|
305
|
+
return () => this.listeners.get(event)?.delete(cb);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private emitMetrics(): void {
|
|
309
|
+
const metrics = this.getMetrics();
|
|
310
|
+
this.listeners.get('metrics')?.forEach((cb) => cb(metrics));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
terminate(): void {
|
|
314
|
+
for (const [, entry] of Array.from(this.workers.entries())) {
|
|
315
|
+
entry.worker.terminate();
|
|
316
|
+
}
|
|
317
|
+
this.workers.clear();
|
|
318
|
+
this.taskQueue.length = 0;
|
|
319
|
+
this.pendingTasks.clear();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import './index.css'
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
import App from "./App";
|
|
3
|
+
import "./index.css";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
<React.StrictMode>
|
|
8
|
-
<App />
|
|
9
|
-
</React.StrictMode>
|
|
10
|
-
)
|
|
5
|
+
createRoot(document.getElementById("root")!).render(<App />);
|