@constela/runtime 0.15.2 → 0.16.1
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/dist/index.d.ts +199 -1
- package/dist/index.js +351 -7
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -50,8 +50,11 @@ interface StateStore {
|
|
|
50
50
|
getPath(name: string, path: string | (string | number)[]): unknown;
|
|
51
51
|
setPath(name: string, path: string | (string | number)[], value: unknown): void;
|
|
52
52
|
subscribeToPath(name: string, path: string | (string | number)[], fn: (value: unknown) => void): () => void;
|
|
53
|
+
serialize(): Record<string, unknown>;
|
|
54
|
+
restore(snapshot: Record<string, unknown>, newDefinitions: StateDefinition[]): void;
|
|
53
55
|
}
|
|
54
56
|
interface StateDefinition {
|
|
57
|
+
name?: string;
|
|
55
58
|
type: string;
|
|
56
59
|
initial: unknown;
|
|
57
60
|
}
|
|
@@ -316,6 +319,11 @@ interface RenderContext {
|
|
|
316
319
|
store: LocalStateStore;
|
|
317
320
|
actions: Record<string, CompiledLocalAction>;
|
|
318
321
|
};
|
|
322
|
+
route?: {
|
|
323
|
+
params: Record<string, string>;
|
|
324
|
+
query: Record<string, string>;
|
|
325
|
+
path: string;
|
|
326
|
+
};
|
|
319
327
|
}
|
|
320
328
|
declare function render(node: CompiledNode, ctx: RenderContext): Node;
|
|
321
329
|
|
|
@@ -385,4 +393,194 @@ interface HydrateOptions {
|
|
|
385
393
|
*/
|
|
386
394
|
declare function hydrateApp(options: HydrateOptions): AppInstance;
|
|
387
395
|
|
|
388
|
-
|
|
396
|
+
/**
|
|
397
|
+
* HMR Client - WebSocket client for Hot Module Replacement
|
|
398
|
+
*
|
|
399
|
+
* This module provides a client-side WebSocket connection for receiving
|
|
400
|
+
* HMR updates from the development server.
|
|
401
|
+
*
|
|
402
|
+
* WebSocket Protocol:
|
|
403
|
+
* - Server -> Client: 'connected' on connection
|
|
404
|
+
* - Server -> Client: 'update' when file changes (file: string, program: CompiledProgram)
|
|
405
|
+
* - Server -> Client: 'error' when compilation fails (file: string, errors: unknown[])
|
|
406
|
+
*/
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Options for creating an HMR client
|
|
410
|
+
*/
|
|
411
|
+
interface HMRClientOptions {
|
|
412
|
+
/** WebSocket server URL (e.g., 'ws://localhost:3001') */
|
|
413
|
+
url: string;
|
|
414
|
+
/** Called when a file update is received */
|
|
415
|
+
onUpdate: (file: string, program: CompiledProgram) => void;
|
|
416
|
+
/** Called when a compilation error is received */
|
|
417
|
+
onError: (file: string, errors: unknown[]) => void;
|
|
418
|
+
/** Called when connection is established */
|
|
419
|
+
onConnect?: () => void;
|
|
420
|
+
/** Called when connection is lost */
|
|
421
|
+
onDisconnect?: () => void;
|
|
422
|
+
/** Initial reconnect delay in milliseconds (default: 1000) */
|
|
423
|
+
initialReconnectDelay?: number;
|
|
424
|
+
/** Maximum reconnect delay in milliseconds (default: 30000) */
|
|
425
|
+
maxReconnectDelay?: number;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* HMR Client interface
|
|
429
|
+
*/
|
|
430
|
+
interface HMRClient {
|
|
431
|
+
/** Establish WebSocket connection */
|
|
432
|
+
connect(): void;
|
|
433
|
+
/** Close WebSocket connection */
|
|
434
|
+
disconnect(): void;
|
|
435
|
+
/** Check if currently connected */
|
|
436
|
+
isConnected(): boolean;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Creates an HMR client for receiving hot updates from the development server.
|
|
440
|
+
*
|
|
441
|
+
* Features:
|
|
442
|
+
* - WebSocket connection management
|
|
443
|
+
* - JSON message parsing
|
|
444
|
+
* - Auto-reconnect with exponential backoff
|
|
445
|
+
* - Connection state tracking
|
|
446
|
+
*
|
|
447
|
+
* @param options - Client configuration options
|
|
448
|
+
* @returns HMRClient interface for managing the connection
|
|
449
|
+
*/
|
|
450
|
+
declare function createHMRClient(options: HMRClientOptions): HMRClient;
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* HMR Handler - Update handler for Hot Module Replacement
|
|
454
|
+
*
|
|
455
|
+
* This module handles applying HMR updates to the running application,
|
|
456
|
+
* preserving state across updates.
|
|
457
|
+
*
|
|
458
|
+
* Update Flow:
|
|
459
|
+
* 1. Serialize current state: state.serialize()
|
|
460
|
+
* 2. Destroy old app: app.destroy()
|
|
461
|
+
* 3. Render new app with new program
|
|
462
|
+
* 4. Restore state: state.restore(snapshot, newDefinitions)
|
|
463
|
+
*/
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Route context for the application
|
|
467
|
+
*/
|
|
468
|
+
interface RouteContext {
|
|
469
|
+
params: Record<string, string>;
|
|
470
|
+
query: Record<string, string>;
|
|
471
|
+
path: string;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Options for creating an HMR handler
|
|
475
|
+
*/
|
|
476
|
+
interface HMRHandlerOptions {
|
|
477
|
+
/** Container element for the application */
|
|
478
|
+
container: HTMLElement;
|
|
479
|
+
/** Initial compiled program */
|
|
480
|
+
program: CompiledProgram;
|
|
481
|
+
/** Optional route context */
|
|
482
|
+
route?: RouteContext;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* HMR Handler interface
|
|
486
|
+
*/
|
|
487
|
+
interface HMRHandler {
|
|
488
|
+
/** Apply a program update while preserving state */
|
|
489
|
+
handleUpdate(program: CompiledProgram): void;
|
|
490
|
+
/** Destroy the handler and cleanup resources */
|
|
491
|
+
destroy(): void;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Creates an HMR handler for applying hot updates to the running application.
|
|
495
|
+
*
|
|
496
|
+
* Update Flow:
|
|
497
|
+
* 1. Serialize current state: state.serialize()
|
|
498
|
+
* 2. Destroy old app: app.destroy()
|
|
499
|
+
* 3. Create new state store from new program
|
|
500
|
+
* 4. Restore state: state.restore(snapshot, newDefinitions)
|
|
501
|
+
* 5. Render new app with restored state
|
|
502
|
+
*
|
|
503
|
+
* @param options - Handler configuration options
|
|
504
|
+
* @returns HMRHandler interface for managing updates
|
|
505
|
+
*/
|
|
506
|
+
declare function createHMRHandler(options: HMRHandlerOptions): HMRHandler;
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* HMR Error Overlay - Display compilation errors during development
|
|
510
|
+
*
|
|
511
|
+
* This module provides an error overlay UI that displays compilation errors
|
|
512
|
+
* as an overlay on the DOM, helping developers quickly identify and fix issues.
|
|
513
|
+
*
|
|
514
|
+
* Features:
|
|
515
|
+
* - Display file path and error details
|
|
516
|
+
* - Show error code and suggestions
|
|
517
|
+
* - Auto-hide when error is fixed
|
|
518
|
+
* - Styled overlay with dark background and high z-index
|
|
519
|
+
*
|
|
520
|
+
* Usage:
|
|
521
|
+
* ```typescript
|
|
522
|
+
* const overlay = createErrorOverlay();
|
|
523
|
+
*
|
|
524
|
+
* // Show error
|
|
525
|
+
* overlay.show({
|
|
526
|
+
* file: '/src/pages/index.json',
|
|
527
|
+
* errors: [{ code: 'PARSE_ERROR', message: 'Unexpected token' }]
|
|
528
|
+
* });
|
|
529
|
+
*
|
|
530
|
+
* // Hide when fixed
|
|
531
|
+
* overlay.hide();
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
/**
|
|
535
|
+
* Error information to display in the overlay
|
|
536
|
+
*/
|
|
537
|
+
interface ErrorInfo {
|
|
538
|
+
/** File path where the error occurred */
|
|
539
|
+
file: string;
|
|
540
|
+
/** Array of error details */
|
|
541
|
+
errors: Array<{
|
|
542
|
+
/** Error code (e.g., 'PARSE_ERROR', 'UNDEFINED_STATE') */
|
|
543
|
+
code?: string;
|
|
544
|
+
/** Human-readable error message */
|
|
545
|
+
message: string;
|
|
546
|
+
/** Path to the problematic element in the JSON (e.g., 'view.children[0].value') */
|
|
547
|
+
path?: string;
|
|
548
|
+
/** Error severity ('error' | 'warning') */
|
|
549
|
+
severity?: string;
|
|
550
|
+
/** Suggestion for fixing the error */
|
|
551
|
+
suggestion?: string;
|
|
552
|
+
}>;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Error overlay interface for displaying and hiding compilation errors
|
|
556
|
+
*/
|
|
557
|
+
interface ErrorOverlay {
|
|
558
|
+
/**
|
|
559
|
+
* Show the error overlay with the given error information
|
|
560
|
+
* @param errorInfo - Error details to display
|
|
561
|
+
*/
|
|
562
|
+
show(errorInfo: ErrorInfo): void;
|
|
563
|
+
/**
|
|
564
|
+
* Hide the error overlay and remove it from the DOM
|
|
565
|
+
*/
|
|
566
|
+
hide(): void;
|
|
567
|
+
/**
|
|
568
|
+
* Check if the overlay is currently visible
|
|
569
|
+
* @returns true if the overlay is visible, false otherwise
|
|
570
|
+
*/
|
|
571
|
+
isVisible(): boolean;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Creates an error overlay instance for displaying compilation errors.
|
|
575
|
+
*
|
|
576
|
+
* The overlay is rendered as a full-screen element with:
|
|
577
|
+
* - Dark semi-transparent background
|
|
578
|
+
* - White text for readability
|
|
579
|
+
* - High z-index to appear above all other content
|
|
580
|
+
* - File path and error details prominently displayed
|
|
581
|
+
*
|
|
582
|
+
* @returns ErrorOverlay interface for managing the overlay
|
|
583
|
+
*/
|
|
584
|
+
declare function createErrorOverlay(): ErrorOverlay;
|
|
585
|
+
|
|
586
|
+
export { type ActionContext, type AppInstance, type Computed, type ConnectionManager, type ErrorInfo, type ErrorOverlay, type EvaluationContext, type HMRClient, type HMRClientOptions, type HMRHandler, type HMRHandlerOptions, type HydrateOptions, type RenderContext, type RouteContext, type Signal, type StateStore, type StylePreset, type TypedStateStore, type WebSocketConnection, type WebSocketHandlers, createApp, createComputed, createConnectionManager, createEffect, createErrorOverlay, createHMRClient, createHMRHandler, createSignal, createStateStore, createTypedStateStore, createWebSocketConnection, evaluate, evaluateStyle, executeAction, hydrateApp, render };
|
package/dist/index.js
CHANGED
|
@@ -319,9 +319,54 @@ function createStateStore(definitions) {
|
|
|
319
319
|
fn(newValue);
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
|
+
},
|
|
323
|
+
serialize() {
|
|
324
|
+
const result = {};
|
|
325
|
+
for (const [name, signal] of signals) {
|
|
326
|
+
const value = signal.get();
|
|
327
|
+
if (typeof value !== "function") {
|
|
328
|
+
result[name] = value;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
},
|
|
333
|
+
restore(snapshot, newDefinitions) {
|
|
334
|
+
for (const def of newDefinitions) {
|
|
335
|
+
const name = def.name;
|
|
336
|
+
if (!name) continue;
|
|
337
|
+
const signal = signals.get(name);
|
|
338
|
+
if (!signal) continue;
|
|
339
|
+
if (!(name in snapshot)) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const snapshotValue = snapshot[name];
|
|
343
|
+
const initialValue = def.initial;
|
|
344
|
+
if (typesMatch(snapshotValue, initialValue)) {
|
|
345
|
+
signal.set(snapshotValue);
|
|
346
|
+
} else {
|
|
347
|
+
console.warn(
|
|
348
|
+
`State field "${name}" type changed. Using new initial value.`
|
|
349
|
+
);
|
|
350
|
+
signal.set(initialValue);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
322
353
|
}
|
|
323
354
|
};
|
|
324
355
|
}
|
|
356
|
+
function typesMatch(a, b2) {
|
|
357
|
+
if (a === null) {
|
|
358
|
+
return b2 === null || typeof b2 === "object" && !Array.isArray(b2);
|
|
359
|
+
}
|
|
360
|
+
if (b2 === null) {
|
|
361
|
+
return typeof a === "object" && !Array.isArray(a);
|
|
362
|
+
}
|
|
363
|
+
const aIsArray = Array.isArray(a);
|
|
364
|
+
const bIsArray = Array.isArray(b2);
|
|
365
|
+
if (aIsArray || bIsArray) {
|
|
366
|
+
return aIsArray && bIsArray;
|
|
367
|
+
}
|
|
368
|
+
return typeof a === typeof b2;
|
|
369
|
+
}
|
|
325
370
|
|
|
326
371
|
// src/state/typed.ts
|
|
327
372
|
function createTypedStateStore(definitions) {
|
|
@@ -13674,7 +13719,7 @@ function renderElement(node, ctx) {
|
|
|
13674
13719
|
}
|
|
13675
13720
|
} else {
|
|
13676
13721
|
const cleanup = createEffect(() => {
|
|
13677
|
-
const value = evaluate(propValue, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
13722
|
+
const value = evaluate(propValue, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } });
|
|
13678
13723
|
applyProp(el, propName, value, useSvgNamespace);
|
|
13679
13724
|
});
|
|
13680
13725
|
ctx.cleanups?.push(cleanup);
|
|
@@ -13729,7 +13774,7 @@ function applyProp(el, propName, value, isSvg = false) {
|
|
|
13729
13774
|
function renderText(node, ctx) {
|
|
13730
13775
|
const textNode = document.createTextNode("");
|
|
13731
13776
|
const cleanup = createEffect(() => {
|
|
13732
|
-
const value = evaluate(node.value, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
13777
|
+
const value = evaluate(node.value, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } });
|
|
13733
13778
|
textNode.textContent = formatValue(value);
|
|
13734
13779
|
});
|
|
13735
13780
|
ctx.cleanups?.push(cleanup);
|
|
@@ -13750,7 +13795,7 @@ function renderIf(node, ctx) {
|
|
|
13750
13795
|
let currentBranch = "none";
|
|
13751
13796
|
let branchCleanups = [];
|
|
13752
13797
|
const effectCleanup = createEffect(() => {
|
|
13753
|
-
const condition = evaluate(node.condition, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
13798
|
+
const condition = evaluate(node.condition, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } });
|
|
13754
13799
|
const shouldShowThen = Boolean(condition);
|
|
13755
13800
|
const newBranch = shouldShowThen ? "then" : node.else ? "else" : "none";
|
|
13756
13801
|
if (newBranch !== currentBranch) {
|
|
@@ -13828,7 +13873,7 @@ function renderEach(node, ctx) {
|
|
|
13828
13873
|
let currentNodes = [];
|
|
13829
13874
|
let itemCleanups = [];
|
|
13830
13875
|
const effectCleanup = createEffect(() => {
|
|
13831
|
-
const items = evaluate(node.items, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
13876
|
+
const items = evaluate(node.items, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } });
|
|
13832
13877
|
if (!hasKey || !node.key) {
|
|
13833
13878
|
for (const cleanup of itemCleanups) {
|
|
13834
13879
|
cleanup();
|
|
@@ -13977,7 +14022,7 @@ function renderMarkdown(node, ctx) {
|
|
|
13977
14022
|
const container = document.createElement("div");
|
|
13978
14023
|
container.className = "constela-markdown";
|
|
13979
14024
|
const cleanup = createEffect(() => {
|
|
13980
|
-
const content = evaluate(node.content, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
14025
|
+
const content = evaluate(node.content, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } });
|
|
13981
14026
|
const html6 = parseMarkdown(String(content ?? ""));
|
|
13982
14027
|
container.innerHTML = html6;
|
|
13983
14028
|
});
|
|
@@ -13992,8 +14037,8 @@ function renderCode(node, ctx) {
|
|
|
13992
14037
|
container.appendChild(pre);
|
|
13993
14038
|
pre.appendChild(codeEl);
|
|
13994
14039
|
const cleanup = createEffect(() => {
|
|
13995
|
-
const language = String(evaluate(node.language, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } }) ?? "plaintext");
|
|
13996
|
-
const content = String(evaluate(node.content, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } }) ?? "");
|
|
14040
|
+
const language = String(evaluate(node.language, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } }) ?? "plaintext");
|
|
14041
|
+
const content = String(evaluate(node.content, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports }, ...ctx.route && { route: ctx.route } }) ?? "");
|
|
13997
14042
|
codeEl.className = `language-${language || "plaintext"}`;
|
|
13998
14043
|
codeEl.dataset["language"] = language || "plaintext";
|
|
13999
14044
|
container.dataset["language"] = language || "plaintext";
|
|
@@ -14113,6 +14158,12 @@ function createStateWithLocalState(globalState, localStore) {
|
|
|
14113
14158
|
},
|
|
14114
14159
|
subscribeToPath(name, path, fn) {
|
|
14115
14160
|
return globalState.subscribeToPath(name, path, fn);
|
|
14161
|
+
},
|
|
14162
|
+
serialize() {
|
|
14163
|
+
return globalState.serialize();
|
|
14164
|
+
},
|
|
14165
|
+
restore(snapshot, newDefinitions) {
|
|
14166
|
+
globalState.restore(snapshot, newDefinitions);
|
|
14116
14167
|
}
|
|
14117
14168
|
};
|
|
14118
14169
|
}
|
|
@@ -15071,11 +15122,304 @@ function createConnectionManager() {
|
|
|
15071
15122
|
}
|
|
15072
15123
|
};
|
|
15073
15124
|
}
|
|
15125
|
+
|
|
15126
|
+
// src/hmr/client.ts
|
|
15127
|
+
var DEFAULT_INITIAL_RECONNECT_DELAY = 1e3;
|
|
15128
|
+
var DEFAULT_MAX_RECONNECT_DELAY = 3e4;
|
|
15129
|
+
function createHMRClient(options) {
|
|
15130
|
+
const {
|
|
15131
|
+
url,
|
|
15132
|
+
onUpdate,
|
|
15133
|
+
onError,
|
|
15134
|
+
onConnect,
|
|
15135
|
+
onDisconnect,
|
|
15136
|
+
initialReconnectDelay = DEFAULT_INITIAL_RECONNECT_DELAY,
|
|
15137
|
+
maxReconnectDelay = DEFAULT_MAX_RECONNECT_DELAY
|
|
15138
|
+
} = options;
|
|
15139
|
+
let ws = null;
|
|
15140
|
+
let isManualDisconnect = false;
|
|
15141
|
+
let reconnectDelay = initialReconnectDelay;
|
|
15142
|
+
let reconnectTimeoutId = null;
|
|
15143
|
+
function handleMessage(event) {
|
|
15144
|
+
try {
|
|
15145
|
+
const message = JSON.parse(event.data);
|
|
15146
|
+
switch (message.type) {
|
|
15147
|
+
case "connected":
|
|
15148
|
+
break;
|
|
15149
|
+
case "update":
|
|
15150
|
+
if (message.file && message.program) {
|
|
15151
|
+
onUpdate(message.file, message.program);
|
|
15152
|
+
}
|
|
15153
|
+
break;
|
|
15154
|
+
case "error":
|
|
15155
|
+
if (message.file && message.errors) {
|
|
15156
|
+
onError(message.file, message.errors);
|
|
15157
|
+
}
|
|
15158
|
+
break;
|
|
15159
|
+
default:
|
|
15160
|
+
break;
|
|
15161
|
+
}
|
|
15162
|
+
} catch {
|
|
15163
|
+
}
|
|
15164
|
+
}
|
|
15165
|
+
function handleOpen() {
|
|
15166
|
+
reconnectDelay = initialReconnectDelay;
|
|
15167
|
+
onConnect?.();
|
|
15168
|
+
}
|
|
15169
|
+
function handleClose() {
|
|
15170
|
+
onDisconnect?.();
|
|
15171
|
+
if (!isManualDisconnect) {
|
|
15172
|
+
scheduleReconnect();
|
|
15173
|
+
}
|
|
15174
|
+
}
|
|
15175
|
+
function handleError() {
|
|
15176
|
+
}
|
|
15177
|
+
function scheduleReconnect() {
|
|
15178
|
+
if (reconnectTimeoutId !== null) {
|
|
15179
|
+
return;
|
|
15180
|
+
}
|
|
15181
|
+
reconnectTimeoutId = setTimeout(() => {
|
|
15182
|
+
reconnectTimeoutId = null;
|
|
15183
|
+
if (!isManualDisconnect) {
|
|
15184
|
+
ws = null;
|
|
15185
|
+
createConnection();
|
|
15186
|
+
reconnectDelay = Math.min(reconnectDelay * 2, maxReconnectDelay);
|
|
15187
|
+
}
|
|
15188
|
+
}, reconnectDelay);
|
|
15189
|
+
}
|
|
15190
|
+
function createConnection() {
|
|
15191
|
+
ws = new WebSocket(url);
|
|
15192
|
+
ws.onopen = handleOpen;
|
|
15193
|
+
ws.onclose = handleClose;
|
|
15194
|
+
ws.onerror = handleError;
|
|
15195
|
+
ws.onmessage = handleMessage;
|
|
15196
|
+
}
|
|
15197
|
+
return {
|
|
15198
|
+
connect() {
|
|
15199
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
15200
|
+
return;
|
|
15201
|
+
}
|
|
15202
|
+
isManualDisconnect = false;
|
|
15203
|
+
if (reconnectTimeoutId !== null) {
|
|
15204
|
+
clearTimeout(reconnectTimeoutId);
|
|
15205
|
+
reconnectTimeoutId = null;
|
|
15206
|
+
}
|
|
15207
|
+
createConnection();
|
|
15208
|
+
},
|
|
15209
|
+
disconnect() {
|
|
15210
|
+
isManualDisconnect = true;
|
|
15211
|
+
if (reconnectTimeoutId !== null) {
|
|
15212
|
+
clearTimeout(reconnectTimeoutId);
|
|
15213
|
+
reconnectTimeoutId = null;
|
|
15214
|
+
}
|
|
15215
|
+
if (ws) {
|
|
15216
|
+
ws.close();
|
|
15217
|
+
ws = null;
|
|
15218
|
+
}
|
|
15219
|
+
},
|
|
15220
|
+
isConnected() {
|
|
15221
|
+
return ws !== null && ws.readyState === WebSocket.OPEN;
|
|
15222
|
+
}
|
|
15223
|
+
};
|
|
15224
|
+
}
|
|
15225
|
+
|
|
15226
|
+
// src/hmr/handler.ts
|
|
15227
|
+
function createHMRApp(program, container, route, existingStateStore) {
|
|
15228
|
+
const state = existingStateStore ?? createStateStore(program.state);
|
|
15229
|
+
let actions;
|
|
15230
|
+
if (program.actions instanceof Map) {
|
|
15231
|
+
actions = {};
|
|
15232
|
+
program.actions.forEach((action, name) => {
|
|
15233
|
+
actions[name] = action;
|
|
15234
|
+
});
|
|
15235
|
+
} else {
|
|
15236
|
+
actions = program.actions;
|
|
15237
|
+
}
|
|
15238
|
+
const cleanups = [];
|
|
15239
|
+
const refs = {};
|
|
15240
|
+
const ctx = {
|
|
15241
|
+
state,
|
|
15242
|
+
actions,
|
|
15243
|
+
locals: {},
|
|
15244
|
+
cleanups,
|
|
15245
|
+
refs,
|
|
15246
|
+
...route && { route },
|
|
15247
|
+
...program.importData && { imports: program.importData }
|
|
15248
|
+
};
|
|
15249
|
+
const rootNode = render(program.view, ctx);
|
|
15250
|
+
container.appendChild(rootNode);
|
|
15251
|
+
const actionCtx = {
|
|
15252
|
+
state,
|
|
15253
|
+
actions,
|
|
15254
|
+
locals: {},
|
|
15255
|
+
refs,
|
|
15256
|
+
...route && { route },
|
|
15257
|
+
...program.importData && { imports: program.importData }
|
|
15258
|
+
};
|
|
15259
|
+
if (program.lifecycle?.onMount) {
|
|
15260
|
+
const onMountAction = actions[program.lifecycle.onMount];
|
|
15261
|
+
if (onMountAction) {
|
|
15262
|
+
void executeAction(onMountAction, actionCtx);
|
|
15263
|
+
}
|
|
15264
|
+
}
|
|
15265
|
+
let destroyed = false;
|
|
15266
|
+
return {
|
|
15267
|
+
stateStore: state,
|
|
15268
|
+
destroy() {
|
|
15269
|
+
if (destroyed) return;
|
|
15270
|
+
destroyed = true;
|
|
15271
|
+
if (program.lifecycle?.onUnmount) {
|
|
15272
|
+
const onUnmountAction = actions[program.lifecycle.onUnmount];
|
|
15273
|
+
if (onUnmountAction) {
|
|
15274
|
+
void executeAction(onUnmountAction, actionCtx);
|
|
15275
|
+
}
|
|
15276
|
+
}
|
|
15277
|
+
for (const cleanup of cleanups) {
|
|
15278
|
+
cleanup();
|
|
15279
|
+
}
|
|
15280
|
+
while (container.firstChild) {
|
|
15281
|
+
container.removeChild(container.firstChild);
|
|
15282
|
+
}
|
|
15283
|
+
},
|
|
15284
|
+
setState(name, value) {
|
|
15285
|
+
if (destroyed) return;
|
|
15286
|
+
state.set(name, value);
|
|
15287
|
+
},
|
|
15288
|
+
getState(name) {
|
|
15289
|
+
return state.get(name);
|
|
15290
|
+
},
|
|
15291
|
+
subscribe(name, fn) {
|
|
15292
|
+
if (destroyed) return () => {
|
|
15293
|
+
};
|
|
15294
|
+
return state.subscribe(name, fn);
|
|
15295
|
+
}
|
|
15296
|
+
};
|
|
15297
|
+
}
|
|
15298
|
+
function createHMRHandler(options) {
|
|
15299
|
+
const { container, program, route } = options;
|
|
15300
|
+
let currentApp = null;
|
|
15301
|
+
let destroyed = false;
|
|
15302
|
+
currentApp = createHMRApp(program, container, route);
|
|
15303
|
+
return {
|
|
15304
|
+
handleUpdate(newProgram) {
|
|
15305
|
+
if (destroyed) {
|
|
15306
|
+
return;
|
|
15307
|
+
}
|
|
15308
|
+
let stateSnapshot = {};
|
|
15309
|
+
if (currentApp) {
|
|
15310
|
+
stateSnapshot = currentApp.stateStore.serialize();
|
|
15311
|
+
currentApp.destroy();
|
|
15312
|
+
currentApp = null;
|
|
15313
|
+
}
|
|
15314
|
+
const newStateStore = createStateStore(newProgram.state);
|
|
15315
|
+
const newDefinitions = Object.entries(newProgram.state).map(
|
|
15316
|
+
([name, def]) => ({
|
|
15317
|
+
name,
|
|
15318
|
+
...def
|
|
15319
|
+
})
|
|
15320
|
+
);
|
|
15321
|
+
newStateStore.restore(stateSnapshot, newDefinitions);
|
|
15322
|
+
currentApp = createHMRApp(newProgram, container, route, newStateStore);
|
|
15323
|
+
},
|
|
15324
|
+
destroy() {
|
|
15325
|
+
if (destroyed) {
|
|
15326
|
+
return;
|
|
15327
|
+
}
|
|
15328
|
+
destroyed = true;
|
|
15329
|
+
if (currentApp) {
|
|
15330
|
+
currentApp.destroy();
|
|
15331
|
+
currentApp = null;
|
|
15332
|
+
}
|
|
15333
|
+
}
|
|
15334
|
+
};
|
|
15335
|
+
}
|
|
15336
|
+
|
|
15337
|
+
// src/utils/escape.ts
|
|
15338
|
+
function escapeHtml(text3) {
|
|
15339
|
+
const div = document.createElement("div");
|
|
15340
|
+
div.textContent = text3;
|
|
15341
|
+
return div.innerHTML;
|
|
15342
|
+
}
|
|
15343
|
+
|
|
15344
|
+
// src/hmr/overlay.ts
|
|
15345
|
+
var OVERLAY_ATTRIBUTE = "data-constela-error-overlay";
|
|
15346
|
+
function createOverlayElement() {
|
|
15347
|
+
const element2 = document.createElement("div");
|
|
15348
|
+
element2.setAttribute(OVERLAY_ATTRIBUTE, "");
|
|
15349
|
+
Object.assign(element2.style, {
|
|
15350
|
+
position: "fixed",
|
|
15351
|
+
top: "0",
|
|
15352
|
+
left: "0",
|
|
15353
|
+
right: "0",
|
|
15354
|
+
bottom: "0",
|
|
15355
|
+
width: "100%",
|
|
15356
|
+
height: "100%",
|
|
15357
|
+
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
|
15358
|
+
color: "#ffffff",
|
|
15359
|
+
zIndex: "9999",
|
|
15360
|
+
overflow: "auto",
|
|
15361
|
+
padding: "24px",
|
|
15362
|
+
boxSizing: "border-box",
|
|
15363
|
+
fontFamily: "monospace",
|
|
15364
|
+
fontSize: "14px",
|
|
15365
|
+
lineHeight: "1.5"
|
|
15366
|
+
});
|
|
15367
|
+
return element2;
|
|
15368
|
+
}
|
|
15369
|
+
function renderErrorContent(errorInfo) {
|
|
15370
|
+
const errorItems = errorInfo.errors.map((error) => {
|
|
15371
|
+
const parts = [];
|
|
15372
|
+
if (error.code) {
|
|
15373
|
+
parts.push(`<span style="color: #ff6b6b; font-weight: bold;">${escapeHtml(error.code)}</span>`);
|
|
15374
|
+
}
|
|
15375
|
+
parts.push(`<div style="margin-top: 4px;">${escapeHtml(error.message)}</div>`);
|
|
15376
|
+
if (error.path) {
|
|
15377
|
+
parts.push(`<div style="color: #888; margin-top: 4px;">at ${escapeHtml(error.path)}</div>`);
|
|
15378
|
+
}
|
|
15379
|
+
if (error.suggestion) {
|
|
15380
|
+
parts.push(`<div style="color: #4ecdc4; margin-top: 8px;">${escapeHtml(error.suggestion)}</div>`);
|
|
15381
|
+
}
|
|
15382
|
+
return `<div style="background: rgba(255, 255, 255, 0.05); padding: 16px; border-radius: 4px; margin-bottom: 12px;">${parts.join("")}</div>`;
|
|
15383
|
+
}).join("");
|
|
15384
|
+
return `
|
|
15385
|
+
<div style="margin-bottom: 24px;">
|
|
15386
|
+
<div style="color: #ff6b6b; font-size: 18px; font-weight: bold; margin-bottom: 8px;">Compilation Error</div>
|
|
15387
|
+
<div style="color: #aaa; word-break: break-all;">${escapeHtml(errorInfo.file)}</div>
|
|
15388
|
+
</div>
|
|
15389
|
+
<div>${errorItems}</div>
|
|
15390
|
+
`;
|
|
15391
|
+
}
|
|
15392
|
+
function createErrorOverlay() {
|
|
15393
|
+
let overlayElement = null;
|
|
15394
|
+
return {
|
|
15395
|
+
show(errorInfo) {
|
|
15396
|
+
if (overlayElement && document.body.contains(overlayElement)) {
|
|
15397
|
+
overlayElement.innerHTML = renderErrorContent(errorInfo);
|
|
15398
|
+
return;
|
|
15399
|
+
}
|
|
15400
|
+
overlayElement = createOverlayElement();
|
|
15401
|
+
overlayElement.innerHTML = renderErrorContent(errorInfo);
|
|
15402
|
+
document.body.appendChild(overlayElement);
|
|
15403
|
+
},
|
|
15404
|
+
hide() {
|
|
15405
|
+
if (overlayElement && document.body.contains(overlayElement)) {
|
|
15406
|
+
document.body.removeChild(overlayElement);
|
|
15407
|
+
}
|
|
15408
|
+
overlayElement = null;
|
|
15409
|
+
},
|
|
15410
|
+
isVisible() {
|
|
15411
|
+
return overlayElement !== null && document.body.contains(overlayElement);
|
|
15412
|
+
}
|
|
15413
|
+
};
|
|
15414
|
+
}
|
|
15074
15415
|
export {
|
|
15075
15416
|
createApp,
|
|
15076
15417
|
createComputed,
|
|
15077
15418
|
createConnectionManager,
|
|
15078
15419
|
createEffect,
|
|
15420
|
+
createErrorOverlay,
|
|
15421
|
+
createHMRClient,
|
|
15422
|
+
createHMRHandler,
|
|
15079
15423
|
createSignal,
|
|
15080
15424
|
createStateStore,
|
|
15081
15425
|
createTypedStateStore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Runtime DOM renderer for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dompurify": "^3.3.1",
|
|
19
19
|
"marked": "^17.0.1",
|
|
20
20
|
"shiki": "^3.20.0",
|
|
21
|
-
"@constela/compiler": "0.11.
|
|
22
|
-
"@constela/core": "0.12.
|
|
21
|
+
"@constela/compiler": "0.11.2",
|
|
22
|
+
"@constela/core": "0.12.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/dompurify": "^3.2.0",
|