@hypen-space/core 0.4.3 → 0.4.5
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 +1 -1
- package/dist/app.d.ts +79 -22
- package/dist/app.js +111 -18
- package/dist/app.js.map +4 -4
- package/dist/components/builtin.js +114 -21
- package/dist/components/builtin.js.map +5 -5
- package/dist/discovery.d.ts +1 -1
- package/dist/discovery.js +112 -19
- package/dist/discovery.js.map +5 -5
- package/dist/engine.browser.d.ts +6 -0
- package/dist/engine.browser.js +192 -5
- package/dist/engine.browser.js.map +5 -4
- package/dist/engine.d.ts +6 -0
- package/dist/engine.js +192 -5
- package/dist/engine.js.map +5 -4
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.js +111 -18
- package/dist/index.browser.js.map +5 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +117 -21
- package/dist/index.js.map +6 -6
- package/dist/managed-router.d.ts +3 -4
- package/dist/remote/client.js +34 -2
- package/dist/remote/client.js.map +3 -3
- package/dist/remote/index.js +472 -26
- package/dist/remote/index.js.map +8 -7
- package/dist/remote/server.d.ts +46 -15
- package/dist/remote/server.js +472 -26
- package/dist/remote/server.js.map +8 -7
- package/dist/result.d.ts +18 -0
- package/package.json +1 -1
- package/src/app.ts +162 -41
- package/src/components/builtin.ts +3 -4
- package/src/discovery.ts +2 -2
- package/src/engine.browser.ts +27 -4
- package/src/engine.ts +27 -4
- package/src/index.browser.ts +0 -2
- package/src/index.ts +3 -2
- package/src/managed-router.ts +5 -6
- package/src/remote/server.ts +116 -21
- package/src/result.ts +48 -0
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +1 -1
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +1 -1
- package/dist/module-registry.d.ts +0 -42
- package/src/module-registry.ts +0 -76
package/dist/remote/index.js
CHANGED
|
@@ -361,7 +361,20 @@ function all(results) {
|
|
|
361
361
|
}
|
|
362
362
|
return Ok(values);
|
|
363
363
|
}
|
|
364
|
-
|
|
364
|
+
function classifyEngineError(err) {
|
|
365
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
366
|
+
if (message.startsWith("Parse error:")) {
|
|
367
|
+
return new ParseError(message);
|
|
368
|
+
}
|
|
369
|
+
if (message.startsWith("Invalid state:") || message.startsWith("Invalid state patch:")) {
|
|
370
|
+
return new StateError(message);
|
|
371
|
+
}
|
|
372
|
+
if (message.startsWith("Parent node not found:")) {
|
|
373
|
+
return new RenderError(message);
|
|
374
|
+
}
|
|
375
|
+
return new RenderError(message);
|
|
376
|
+
}
|
|
377
|
+
var HypenError, ActionError, ConnectionError, StateError, ParseError, RenderError;
|
|
365
378
|
var init_result = __esm(() => {
|
|
366
379
|
HypenError = class HypenError extends Error {
|
|
367
380
|
code;
|
|
@@ -411,6 +424,25 @@ var init_result = __esm(() => {
|
|
|
411
424
|
this.path = path;
|
|
412
425
|
}
|
|
413
426
|
};
|
|
427
|
+
ParseError = class ParseError extends HypenError {
|
|
428
|
+
source;
|
|
429
|
+
constructor(message, source, cause) {
|
|
430
|
+
super("PARSE_ERROR", message, {
|
|
431
|
+
context: { source },
|
|
432
|
+
cause: cause instanceof Error ? cause : undefined
|
|
433
|
+
});
|
|
434
|
+
this.name = "ParseError";
|
|
435
|
+
this.source = source;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
RenderError = class RenderError extends HypenError {
|
|
439
|
+
constructor(message, cause) {
|
|
440
|
+
super("RENDER_ERROR", message, {
|
|
441
|
+
cause: cause instanceof Error ? cause : undefined
|
|
442
|
+
});
|
|
443
|
+
this.name = "RenderError";
|
|
444
|
+
}
|
|
445
|
+
};
|
|
414
446
|
});
|
|
415
447
|
|
|
416
448
|
// src/logger.ts
|
|
@@ -632,9 +664,11 @@ class HypenAppBuilder {
|
|
|
632
664
|
expireHandler;
|
|
633
665
|
errorHandler;
|
|
634
666
|
template;
|
|
635
|
-
|
|
667
|
+
_registry;
|
|
668
|
+
constructor(initialState, options, registry) {
|
|
636
669
|
this.initialState = initialState;
|
|
637
670
|
this.options = options || {};
|
|
671
|
+
this._registry = registry;
|
|
638
672
|
}
|
|
639
673
|
onCreated(fn) {
|
|
640
674
|
this.createdHandler = fn;
|
|
@@ -670,7 +704,7 @@ class HypenAppBuilder {
|
|
|
670
704
|
}
|
|
671
705
|
build() {
|
|
672
706
|
const stateKeys = this.initialState !== null && typeof this.initialState === "object" ? Object.keys(this.initialState) : [];
|
|
673
|
-
|
|
707
|
+
const definition = {
|
|
674
708
|
name: this.options.name,
|
|
675
709
|
actions: Array.from(this.actionHandlers.keys()),
|
|
676
710
|
stateKeys,
|
|
@@ -688,12 +722,46 @@ class HypenAppBuilder {
|
|
|
688
722
|
onError: this.errorHandler
|
|
689
723
|
}
|
|
690
724
|
};
|
|
725
|
+
if (this.options.name && this._registry) {
|
|
726
|
+
this._registry.set(this.options.name, definition);
|
|
727
|
+
}
|
|
728
|
+
return definition;
|
|
691
729
|
}
|
|
692
730
|
}
|
|
693
731
|
|
|
694
732
|
class HypenApp {
|
|
733
|
+
_registry = new Map;
|
|
695
734
|
defineState(initial, options) {
|
|
696
|
-
return new HypenAppBuilder(initial, options);
|
|
735
|
+
return new HypenAppBuilder(initial, options, this._registry);
|
|
736
|
+
}
|
|
737
|
+
module(name) {
|
|
738
|
+
const registry = this._registry;
|
|
739
|
+
return {
|
|
740
|
+
defineState: (initial, options) => {
|
|
741
|
+
return new HypenAppBuilder(initial, { ...options, name }, registry);
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
get(name) {
|
|
746
|
+
return this._registry.get(name);
|
|
747
|
+
}
|
|
748
|
+
has(name) {
|
|
749
|
+
return this._registry.has(name);
|
|
750
|
+
}
|
|
751
|
+
get components() {
|
|
752
|
+
return this._registry;
|
|
753
|
+
}
|
|
754
|
+
getNames() {
|
|
755
|
+
return Array.from(this._registry.keys());
|
|
756
|
+
}
|
|
757
|
+
get size() {
|
|
758
|
+
return this._registry.size;
|
|
759
|
+
}
|
|
760
|
+
unregister(name) {
|
|
761
|
+
this._registry.delete(name);
|
|
762
|
+
}
|
|
763
|
+
clear() {
|
|
764
|
+
this._registry.clear();
|
|
697
765
|
}
|
|
698
766
|
}
|
|
699
767
|
|
|
@@ -702,13 +770,13 @@ class HypenModuleInstance {
|
|
|
702
770
|
definition;
|
|
703
771
|
state;
|
|
704
772
|
isDestroyed = false;
|
|
705
|
-
|
|
773
|
+
router;
|
|
706
774
|
globalContext;
|
|
707
775
|
stateChangeCallbacks = [];
|
|
708
|
-
constructor(engine, definition,
|
|
776
|
+
constructor(engine, definition, router, globalContext) {
|
|
709
777
|
this.engine = engine;
|
|
710
778
|
this.definition = definition;
|
|
711
|
-
this.
|
|
779
|
+
this.router = router ?? null;
|
|
712
780
|
this.globalContext = globalContext;
|
|
713
781
|
const statePrefix = (definition.name || "").toLowerCase();
|
|
714
782
|
this.state = createObservableState(definition.initialState, {
|
|
@@ -730,14 +798,10 @@ class HypenModuleInstance {
|
|
|
730
798
|
payload: action.payload,
|
|
731
799
|
sender: action.sender
|
|
732
800
|
};
|
|
733
|
-
const next = {
|
|
734
|
-
router: this.routerContext?.root || null
|
|
735
|
-
};
|
|
736
801
|
const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
|
|
737
802
|
const result = await this.executeAction(actionName, handler, {
|
|
738
803
|
action: actionCtx,
|
|
739
804
|
state: this.state,
|
|
740
|
-
next,
|
|
741
805
|
context
|
|
742
806
|
});
|
|
743
807
|
if (!result.ok) {
|
|
@@ -750,6 +814,21 @@ class HypenModuleInstance {
|
|
|
750
814
|
}
|
|
751
815
|
});
|
|
752
816
|
}
|
|
817
|
+
this.engine.onAction("__hypen_bind", (action) => {
|
|
818
|
+
const payload = action.payload;
|
|
819
|
+
if (!payload?.path)
|
|
820
|
+
return;
|
|
821
|
+
const segments = payload.path.split(".");
|
|
822
|
+
let target = this.state;
|
|
823
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
824
|
+
const seg = segments[i];
|
|
825
|
+
target = target?.[seg];
|
|
826
|
+
if (target == null)
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const lastSeg = segments[segments.length - 1];
|
|
830
|
+
target[lastSeg] = payload.value;
|
|
831
|
+
});
|
|
753
832
|
this.callCreatedHandler();
|
|
754
833
|
}
|
|
755
834
|
createGlobalContextAPI() {
|
|
@@ -763,11 +842,9 @@ class HypenModuleInstance {
|
|
|
763
842
|
getModuleIds: () => ctx.getModuleIds(),
|
|
764
843
|
getGlobalState: () => ctx.getGlobalState(),
|
|
765
844
|
emit: (event, payload) => ctx.emit(event, payload),
|
|
766
|
-
on: (event, handler) => ctx.on(event, handler)
|
|
845
|
+
on: (event, handler) => ctx.on(event, handler),
|
|
846
|
+
router: this.router
|
|
767
847
|
};
|
|
768
|
-
if (ctx.__router) {
|
|
769
|
-
api.__router = ctx.__router;
|
|
770
|
-
}
|
|
771
848
|
if (ctx.__hypenEngine) {
|
|
772
849
|
api.__hypenEngine = ctx.__hypenEngine;
|
|
773
850
|
}
|
|
@@ -818,7 +895,15 @@ class HypenModuleInstance {
|
|
|
818
895
|
async callCreatedHandler() {
|
|
819
896
|
if (this.definition.handlers.onCreated) {
|
|
820
897
|
const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
|
|
821
|
-
|
|
898
|
+
try {
|
|
899
|
+
await this.definition.handlers.onCreated(this.state, context);
|
|
900
|
+
} catch (e) {
|
|
901
|
+
const error = e instanceof HypenError ? e : new ActionError("onCreated", e);
|
|
902
|
+
const shouldRethrow = await this.handleError(error, { lifecycle: "created" });
|
|
903
|
+
if (shouldRethrow) {
|
|
904
|
+
throw error;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
822
907
|
}
|
|
823
908
|
}
|
|
824
909
|
onStateChange(callback) {
|
|
@@ -828,7 +913,15 @@ class HypenModuleInstance {
|
|
|
828
913
|
if (this.isDestroyed)
|
|
829
914
|
return;
|
|
830
915
|
if (this.definition.handlers.onDestroyed) {
|
|
831
|
-
|
|
916
|
+
try {
|
|
917
|
+
await this.definition.handlers.onDestroyed(this.state);
|
|
918
|
+
} catch (e) {
|
|
919
|
+
const error = e instanceof HypenError ? e : new ActionError("onDestroyed", e);
|
|
920
|
+
const shouldRethrow = await this.handleError(error, { lifecycle: "destroyed" });
|
|
921
|
+
if (shouldRethrow) {
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
832
925
|
}
|
|
833
926
|
this.isDestroyed = true;
|
|
834
927
|
}
|
|
@@ -1378,6 +1471,7 @@ class RemoteEngine {
|
|
|
1378
1471
|
|
|
1379
1472
|
// src/engine.ts
|
|
1380
1473
|
init_logger();
|
|
1474
|
+
init_result();
|
|
1381
1475
|
import { WasmEngine } from "../wasm-node/hypen_engine.js";
|
|
1382
1476
|
var log5 = frameworkLoggers.engine;
|
|
1383
1477
|
function unwrapForWasm(value) {
|
|
@@ -1424,7 +1518,11 @@ class Engine {
|
|
|
1424
1518
|
}
|
|
1425
1519
|
renderSource(source) {
|
|
1426
1520
|
const engine = this.ensureInitialized();
|
|
1427
|
-
|
|
1521
|
+
try {
|
|
1522
|
+
engine.renderSource(source);
|
|
1523
|
+
} catch (err) {
|
|
1524
|
+
throw classifyEngineError(err);
|
|
1525
|
+
}
|
|
1428
1526
|
}
|
|
1429
1527
|
renderLazyComponent(source) {
|
|
1430
1528
|
const engine = this.ensureInitialized();
|
|
@@ -1432,14 +1530,22 @@ class Engine {
|
|
|
1432
1530
|
}
|
|
1433
1531
|
renderInto(source, parentNodeId, state) {
|
|
1434
1532
|
const engine = this.ensureInitialized();
|
|
1435
|
-
|
|
1533
|
+
try {
|
|
1534
|
+
engine.renderInto(source, parentNodeId, unwrapForWasm(state));
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
throw classifyEngineError(err);
|
|
1537
|
+
}
|
|
1436
1538
|
}
|
|
1437
1539
|
notifyStateChange(paths, values) {
|
|
1438
1540
|
const engine = this.ensureInitialized();
|
|
1439
1541
|
if (paths.length === 0) {
|
|
1440
1542
|
return;
|
|
1441
1543
|
}
|
|
1442
|
-
|
|
1544
|
+
try {
|
|
1545
|
+
engine.updateStateSparse(paths, unwrapForWasm(values));
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
throw classifyEngineError(err);
|
|
1548
|
+
}
|
|
1443
1549
|
log5.debug("State changed (sparse):", paths);
|
|
1444
1550
|
}
|
|
1445
1551
|
notifyStateChangeFull(paths, currentState) {
|
|
@@ -1455,7 +1561,11 @@ class Engine {
|
|
|
1455
1561
|
}
|
|
1456
1562
|
dispatchAction(name, payload) {
|
|
1457
1563
|
const engine = this.ensureInitialized();
|
|
1458
|
-
|
|
1564
|
+
try {
|
|
1565
|
+
engine.dispatchAction(name, payload ?? null);
|
|
1566
|
+
} catch (err) {
|
|
1567
|
+
throw classifyEngineError(err);
|
|
1568
|
+
}
|
|
1459
1569
|
}
|
|
1460
1570
|
onAction(actionName, handler) {
|
|
1461
1571
|
const engine = this.ensureInitialized();
|
|
@@ -1481,6 +1591,297 @@ class Engine {
|
|
|
1481
1591
|
}
|
|
1482
1592
|
}
|
|
1483
1593
|
|
|
1594
|
+
// src/discovery.ts
|
|
1595
|
+
init_logger();
|
|
1596
|
+
import { existsSync, readdirSync, readFileSync, watch } from "fs";
|
|
1597
|
+
import { join, basename, resolve, relative } from "path";
|
|
1598
|
+
async function discoverComponents(baseDir, options = {}) {
|
|
1599
|
+
const {
|
|
1600
|
+
patterns = ["single-file", "folder", "sibling", "index"],
|
|
1601
|
+
recursive = true,
|
|
1602
|
+
debug = false
|
|
1603
|
+
} = options;
|
|
1604
|
+
const log6 = debug ? (...args) => frameworkLoggers.discovery.debug(...args) : () => {};
|
|
1605
|
+
const resolvedDir = resolve(baseDir);
|
|
1606
|
+
const components = [];
|
|
1607
|
+
const seen = new Set;
|
|
1608
|
+
log6("Scanning directory:", resolvedDir);
|
|
1609
|
+
log6("Patterns:", patterns);
|
|
1610
|
+
const addTwoFileComponent = (name, hypenPath, modulePath) => {
|
|
1611
|
+
if (seen.has(name)) {
|
|
1612
|
+
log6(`Skipping duplicate: ${name}`);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
seen.add(name);
|
|
1616
|
+
const templateRaw = readFileSync(hypenPath, "utf-8");
|
|
1617
|
+
const template = templateRaw.trim();
|
|
1618
|
+
components.push({
|
|
1619
|
+
name,
|
|
1620
|
+
hypenPath,
|
|
1621
|
+
modulePath,
|
|
1622
|
+
template,
|
|
1623
|
+
hasModule: modulePath !== null,
|
|
1624
|
+
isSingleFile: false
|
|
1625
|
+
});
|
|
1626
|
+
log6(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
|
|
1627
|
+
};
|
|
1628
|
+
const addSingleFileComponent = (name, modulePath, template) => {
|
|
1629
|
+
if (seen.has(name)) {
|
|
1630
|
+
log6(`Skipping duplicate: ${name}`);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
seen.add(name);
|
|
1634
|
+
components.push({
|
|
1635
|
+
name,
|
|
1636
|
+
hypenPath: null,
|
|
1637
|
+
modulePath,
|
|
1638
|
+
template,
|
|
1639
|
+
hasModule: true,
|
|
1640
|
+
isSingleFile: true
|
|
1641
|
+
});
|
|
1642
|
+
log6(`Found: ${name} (single-file with inline template)`);
|
|
1643
|
+
};
|
|
1644
|
+
const scanForFolderComponents = (dir) => {
|
|
1645
|
+
if (!existsSync(dir))
|
|
1646
|
+
return;
|
|
1647
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1648
|
+
for (const entry of entries) {
|
|
1649
|
+
if (!entry.isDirectory())
|
|
1650
|
+
continue;
|
|
1651
|
+
const folderPath = join(dir, entry.name);
|
|
1652
|
+
const componentName = entry.name;
|
|
1653
|
+
if (patterns.includes("folder")) {
|
|
1654
|
+
const hypenPath = join(folderPath, "component.hypen");
|
|
1655
|
+
if (existsSync(hypenPath)) {
|
|
1656
|
+
const modulePath = join(folderPath, "component.ts");
|
|
1657
|
+
addTwoFileComponent(componentName, hypenPath, existsSync(modulePath) ? modulePath : null);
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (patterns.includes("index")) {
|
|
1662
|
+
const hypenPath = join(folderPath, "index.hypen");
|
|
1663
|
+
if (existsSync(hypenPath)) {
|
|
1664
|
+
const modulePath = join(folderPath, "index.ts");
|
|
1665
|
+
addTwoFileComponent(componentName, hypenPath, existsSync(modulePath) ? modulePath : null);
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (recursive) {
|
|
1670
|
+
scanForFolderComponents(folderPath);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
const scanForSiblingComponents = (dir) => {
|
|
1675
|
+
if (!existsSync(dir))
|
|
1676
|
+
return;
|
|
1677
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1678
|
+
for (const entry of entries) {
|
|
1679
|
+
if (entry.isDirectory()) {
|
|
1680
|
+
if (recursive) {
|
|
1681
|
+
scanForSiblingComponents(join(dir, entry.name));
|
|
1682
|
+
}
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
if (!entry.name.endsWith(".hypen"))
|
|
1686
|
+
continue;
|
|
1687
|
+
const hypenPath = join(dir, entry.name);
|
|
1688
|
+
const baseName = basename(entry.name, ".hypen");
|
|
1689
|
+
if (baseName === "component" || baseName === "index")
|
|
1690
|
+
continue;
|
|
1691
|
+
const modulePath = join(dir, `${baseName}.ts`);
|
|
1692
|
+
addTwoFileComponent(baseName, hypenPath, existsSync(modulePath) ? modulePath : null);
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
const scanForSingleFileComponents = async (dir) => {
|
|
1696
|
+
if (!existsSync(dir))
|
|
1697
|
+
return;
|
|
1698
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1699
|
+
for (const entry of entries) {
|
|
1700
|
+
if (entry.isDirectory()) {
|
|
1701
|
+
if (recursive) {
|
|
1702
|
+
await scanForSingleFileComponents(join(dir, entry.name));
|
|
1703
|
+
}
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
if (!entry.name.endsWith(".ts"))
|
|
1707
|
+
continue;
|
|
1708
|
+
if (entry.name.startsWith(".") || entry.name.includes(".test.") || entry.name.includes(".spec."))
|
|
1709
|
+
continue;
|
|
1710
|
+
const baseName = basename(entry.name, ".ts");
|
|
1711
|
+
if (baseName === "component" || baseName === "index")
|
|
1712
|
+
continue;
|
|
1713
|
+
const hypenPath = join(dir, `${baseName}.hypen`);
|
|
1714
|
+
if (existsSync(hypenPath))
|
|
1715
|
+
continue;
|
|
1716
|
+
const modulePath = join(dir, entry.name);
|
|
1717
|
+
const content = readFileSync(modulePath, "utf-8");
|
|
1718
|
+
if (content.includes(".ui(") || content.includes(".ui(hypen")) {
|
|
1719
|
+
try {
|
|
1720
|
+
const moduleExport = await import(modulePath);
|
|
1721
|
+
const module = moduleExport.default;
|
|
1722
|
+
if (module && typeof module === "object" && module.template) {
|
|
1723
|
+
addSingleFileComponent(baseName, modulePath, module.template);
|
|
1724
|
+
}
|
|
1725
|
+
} catch (e) {
|
|
1726
|
+
log6(`Failed to import potential single-file component: ${entry.name}`, e);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
if (patterns.includes("folder") || patterns.includes("index")) {
|
|
1732
|
+
scanForFolderComponents(resolvedDir);
|
|
1733
|
+
}
|
|
1734
|
+
if (patterns.includes("sibling")) {
|
|
1735
|
+
scanForSiblingComponents(resolvedDir);
|
|
1736
|
+
}
|
|
1737
|
+
if (patterns.includes("single-file")) {
|
|
1738
|
+
await scanForSingleFileComponents(resolvedDir);
|
|
1739
|
+
}
|
|
1740
|
+
log6(`Discovered ${components.length} components`);
|
|
1741
|
+
return components;
|
|
1742
|
+
}
|
|
1743
|
+
async function loadDiscoveredComponents(components) {
|
|
1744
|
+
const { app: app2 } = await Promise.resolve().then(() => (init_app(), exports_app));
|
|
1745
|
+
const loaded = new Map;
|
|
1746
|
+
for (const component of components) {
|
|
1747
|
+
let module;
|
|
1748
|
+
let template = component.template;
|
|
1749
|
+
if (component.modulePath) {
|
|
1750
|
+
const moduleExport = await import(component.modulePath);
|
|
1751
|
+
module = moduleExport.default;
|
|
1752
|
+
if (component.isSingleFile && module.template) {
|
|
1753
|
+
template = module.template;
|
|
1754
|
+
}
|
|
1755
|
+
} else {
|
|
1756
|
+
module = app2.defineState({}).build();
|
|
1757
|
+
}
|
|
1758
|
+
loaded.set(component.name, {
|
|
1759
|
+
name: component.name,
|
|
1760
|
+
module,
|
|
1761
|
+
template
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
return loaded;
|
|
1765
|
+
}
|
|
1766
|
+
function watchComponents(baseDir, options = {}) {
|
|
1767
|
+
const resolvedDir = resolve(baseDir);
|
|
1768
|
+
const {
|
|
1769
|
+
onChange,
|
|
1770
|
+
onAdd,
|
|
1771
|
+
onRemove,
|
|
1772
|
+
onUpdate,
|
|
1773
|
+
debug = false,
|
|
1774
|
+
...discoveryOptions
|
|
1775
|
+
} = options;
|
|
1776
|
+
const log6 = debug ? (...args) => frameworkLoggers.discovery.debug("[watch]", ...args) : () => {};
|
|
1777
|
+
let currentComponents = new Map;
|
|
1778
|
+
let debounceTimer = null;
|
|
1779
|
+
const initialScan = async () => {
|
|
1780
|
+
const components = await discoverComponents(resolvedDir, discoveryOptions);
|
|
1781
|
+
currentComponents = new Map(components.map((c) => [c.name, c]));
|
|
1782
|
+
onChange?.(components);
|
|
1783
|
+
};
|
|
1784
|
+
const rescan = async () => {
|
|
1785
|
+
if (debounceTimer) {
|
|
1786
|
+
clearTimeout(debounceTimer);
|
|
1787
|
+
}
|
|
1788
|
+
debounceTimer = setTimeout(async () => {
|
|
1789
|
+
log6("Rescanning...");
|
|
1790
|
+
const newComponents = await discoverComponents(resolvedDir, discoveryOptions);
|
|
1791
|
+
const newMap = new Map(newComponents.map((c) => [c.name, c]));
|
|
1792
|
+
for (const [name, component] of newMap) {
|
|
1793
|
+
const existing = currentComponents.get(name);
|
|
1794
|
+
if (!existing) {
|
|
1795
|
+
log6("Added:", name);
|
|
1796
|
+
onAdd?.(component);
|
|
1797
|
+
} else if (existing.template !== component.template || existing.modulePath !== component.modulePath) {
|
|
1798
|
+
log6("Updated:", name);
|
|
1799
|
+
onUpdate?.(component);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
for (const name of currentComponents.keys()) {
|
|
1803
|
+
if (!newMap.has(name)) {
|
|
1804
|
+
log6("Removed:", name);
|
|
1805
|
+
onRemove?.(name);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
currentComponents = newMap;
|
|
1809
|
+
onChange?.(newComponents);
|
|
1810
|
+
}, 100);
|
|
1811
|
+
};
|
|
1812
|
+
const watcher = watch(resolvedDir, { recursive: true }, (event, filename) => {
|
|
1813
|
+
if (!filename)
|
|
1814
|
+
return;
|
|
1815
|
+
if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
|
|
1816
|
+
log6("File changed:", filename);
|
|
1817
|
+
rescan();
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
initialScan();
|
|
1821
|
+
return {
|
|
1822
|
+
stop: () => {
|
|
1823
|
+
watcher.close();
|
|
1824
|
+
if (debounceTimer) {
|
|
1825
|
+
clearTimeout(debounceTimer);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
async function generateComponentsCode(baseDir, options = {}) {
|
|
1831
|
+
const components = await discoverComponents(baseDir, options);
|
|
1832
|
+
const resolvedDir = resolve(baseDir);
|
|
1833
|
+
const outputBase = options.outputDir ? resolve(options.outputDir) : resolvedDir;
|
|
1834
|
+
let code = `/**
|
|
1835
|
+
* Auto-generated component imports
|
|
1836
|
+
* Generated by @hypen-space/core discovery
|
|
1837
|
+
*/
|
|
1838
|
+
|
|
1839
|
+
`;
|
|
1840
|
+
for (const component of components) {
|
|
1841
|
+
let importPath = null;
|
|
1842
|
+
if (component.modulePath) {
|
|
1843
|
+
const rel = relative(outputBase, component.modulePath).replace(/\.ts$/, ".js");
|
|
1844
|
+
importPath = rel.startsWith(".") ? rel : "./" + rel;
|
|
1845
|
+
}
|
|
1846
|
+
if (importPath) {
|
|
1847
|
+
code += `import ${component.name}Module from "${importPath}";
|
|
1848
|
+
`;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
code += `
|
|
1852
|
+
import { app } from "@hypen-space/core";
|
|
1853
|
+
|
|
1854
|
+
`;
|
|
1855
|
+
for (const component of components) {
|
|
1856
|
+
if (component.isSingleFile) {
|
|
1857
|
+
code += `export const ${component.name} = {
|
|
1858
|
+
module: ${component.name}Module,
|
|
1859
|
+
template: ${component.name}Module.template,
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
`;
|
|
1863
|
+
} else if (component.hasModule) {
|
|
1864
|
+
const templateJson = JSON.stringify(component.template);
|
|
1865
|
+
code += `export const ${component.name} = {
|
|
1866
|
+
module: ${component.name}Module,
|
|
1867
|
+
template: ${templateJson},
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
`;
|
|
1871
|
+
} else {
|
|
1872
|
+
const templateJson = JSON.stringify(component.template);
|
|
1873
|
+
code += `const ${component.name}Module = app.defineState({}).build();
|
|
1874
|
+
export const ${component.name} = {
|
|
1875
|
+
module: ${component.name}Module,
|
|
1876
|
+
template: ${templateJson},
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
`;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return code;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1484
1885
|
// src/remote/server.ts
|
|
1485
1886
|
init_app();
|
|
1486
1887
|
|
|
@@ -1616,6 +2017,8 @@ class RemoteServer {
|
|
|
1616
2017
|
_module = null;
|
|
1617
2018
|
_moduleName = "App";
|
|
1618
2019
|
_ui = "";
|
|
2020
|
+
_sourceDir = null;
|
|
2021
|
+
_discoveredComponents = new Map;
|
|
1619
2022
|
_config = {};
|
|
1620
2023
|
_sessionConfig = {};
|
|
1621
2024
|
_onConnectionCallbacks = [];
|
|
@@ -1633,6 +2036,10 @@ class RemoteServer {
|
|
|
1633
2036
|
this._ui = dsl;
|
|
1634
2037
|
return this;
|
|
1635
2038
|
}
|
|
2039
|
+
source(dir) {
|
|
2040
|
+
this._sourceDir = dir;
|
|
2041
|
+
return this;
|
|
2042
|
+
}
|
|
1636
2043
|
config(config2) {
|
|
1637
2044
|
this._config = { ...this._config, ...config2 };
|
|
1638
2045
|
return this;
|
|
@@ -1649,12 +2056,15 @@ class RemoteServer {
|
|
|
1649
2056
|
this._onDisconnectionCallbacks.push(callback);
|
|
1650
2057
|
return this;
|
|
1651
2058
|
}
|
|
1652
|
-
listen(port) {
|
|
2059
|
+
async listen(port) {
|
|
1653
2060
|
if (!this._module) {
|
|
1654
2061
|
throw new Error("Module not set. Call .module() before .listen()");
|
|
1655
2062
|
}
|
|
2063
|
+
if (this._sourceDir) {
|
|
2064
|
+
await this.discoverFromSource();
|
|
2065
|
+
}
|
|
1656
2066
|
if (!this._ui) {
|
|
1657
|
-
throw new Error("UI not set. Call .ui() before .listen()");
|
|
2067
|
+
throw new Error("UI not set. Call .ui() or .source() before .listen()");
|
|
1658
2068
|
}
|
|
1659
2069
|
this.sessionManager = new SessionManager(this._sessionConfig);
|
|
1660
2070
|
const finalPort = port ?? this._config.port ?? 3000;
|
|
@@ -1708,12 +2118,42 @@ class RemoteServer {
|
|
|
1708
2118
|
const port = this._config.port ?? 3000;
|
|
1709
2119
|
return `ws://${hostname}:${port}`;
|
|
1710
2120
|
}
|
|
2121
|
+
async discoverFromSource() {
|
|
2122
|
+
const dir = this._sourceDir;
|
|
2123
|
+
log6.info(`Discovering components from ${dir}...`);
|
|
2124
|
+
const discovered = await discoverComponents(dir);
|
|
2125
|
+
const loaded = await loadDiscoveredComponents(discovered);
|
|
2126
|
+
for (const [name, { module, template }] of loaded) {
|
|
2127
|
+
this._discoveredComponents.set(name, { template, module });
|
|
2128
|
+
}
|
|
2129
|
+
log6.info(`Discovered ${this._discoveredComponents.size} components: ${Array.from(this._discoveredComponents.keys()).join(", ")}`);
|
|
2130
|
+
if (!this._ui) {
|
|
2131
|
+
const entry = this._discoveredComponents.get(this._moduleName);
|
|
2132
|
+
if (entry) {
|
|
2133
|
+
this._ui = entry.template;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
setupComponentResolver(engine) {
|
|
2138
|
+
if (this._discoveredComponents.size === 0)
|
|
2139
|
+
return;
|
|
2140
|
+
engine.setComponentResolver((componentName, _contextPath) => {
|
|
2141
|
+
const comp = this._discoveredComponents.get(componentName);
|
|
2142
|
+
if (!comp)
|
|
2143
|
+
return null;
|
|
2144
|
+
return {
|
|
2145
|
+
source: comp.template,
|
|
2146
|
+
path: componentName
|
|
2147
|
+
};
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
1711
2150
|
async handleOpen(ws) {
|
|
1712
2151
|
try {
|
|
1713
2152
|
const clientId = `client_${this.nextClientId++}`;
|
|
1714
2153
|
const connectedAt = new Date;
|
|
1715
2154
|
const engine = new Engine;
|
|
1716
2155
|
await engine.init();
|
|
2156
|
+
this.setupComponentResolver(engine);
|
|
1717
2157
|
const moduleInstance = new HypenModuleInstance(engine, this._module);
|
|
1718
2158
|
const clientData = {
|
|
1719
2159
|
id: clientId,
|
|
@@ -1961,8 +2401,14 @@ class RemoteServer {
|
|
|
1961
2401
|
});
|
|
1962
2402
|
}
|
|
1963
2403
|
}
|
|
1964
|
-
function serve(options) {
|
|
1965
|
-
const server = new RemoteServer().module(options.moduleName ?? "App", options.module)
|
|
2404
|
+
async function serve(options) {
|
|
2405
|
+
const server = new RemoteServer().module(options.moduleName ?? "App", options.module);
|
|
2406
|
+
if (options.source) {
|
|
2407
|
+
server.source(options.source);
|
|
2408
|
+
}
|
|
2409
|
+
if (options.ui) {
|
|
2410
|
+
server.ui(options.ui);
|
|
2411
|
+
}
|
|
1966
2412
|
if (options.port || options.hostname) {
|
|
1967
2413
|
server.config({
|
|
1968
2414
|
port: options.port,
|
|
@@ -1987,4 +2433,4 @@ export {
|
|
|
1987
2433
|
RemoteEngine
|
|
1988
2434
|
};
|
|
1989
2435
|
|
|
1990
|
-
//# debugId=
|
|
2436
|
+
//# debugId=B02EBC5F8B52753464756E2164756E21
|