@hypen-space/core 0.4.1 → 0.4.3
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/app.d.ts +322 -0
- package/dist/components/builtin.d.ts +50 -0
- package/dist/context.d.ts +90 -0
- package/dist/discovery.d.ts +90 -0
- package/dist/discovery.js +10 -5
- package/dist/discovery.js.map +3 -3
- package/dist/disposable.d.ts +122 -0
- package/dist/engine.browser.d.ts +95 -0
- package/dist/engine.d.ts +79 -0
- package/dist/events.d.ts +78 -0
- package/dist/hypen.d.ts +127 -0
- package/dist/index.browser.d.ts +25 -0
- package/dist/index.browser.js +6 -2
- package/dist/index.browser.js.map +3 -3
- package/dist/index.d.ts +76 -0
- package/dist/index.js +238 -1196
- package/dist/index.js.map +7 -12
- package/dist/loader.d.ts +51 -0
- package/dist/logger.d.ts +141 -0
- package/dist/managed-router.d.ts +60 -0
- package/dist/module-registry.d.ts +42 -0
- package/dist/plugin.d.ts +39 -0
- package/dist/remote/client.d.ts +154 -0
- package/dist/remote/index.d.ts +13 -0
- package/dist/remote/server.d.ts +142 -0
- package/dist/remote/session.d.ts +115 -0
- package/dist/remote/types.d.ts +98 -0
- package/dist/renderer.d.ts +54 -0
- package/dist/renderer.js +6 -2
- package/dist/renderer.js.map +3 -3
- package/dist/resolver.d.ts +95 -0
- package/dist/result.d.ts +116 -0
- package/dist/retry.d.ts +150 -0
- package/dist/router.d.ts +94 -0
- package/dist/state.d.ts +42 -0
- package/dist/types.d.ts +30 -0
- package/package.json +2 -1
- 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/index.js
CHANGED
|
@@ -895,7 +895,11 @@ class BaseRenderer {
|
|
|
895
895
|
|
|
896
896
|
class ConsoleRenderer {
|
|
897
897
|
applyPatches(patches) {
|
|
898
|
-
|
|
898
|
+
console.group("Hypen Patches");
|
|
899
|
+
for (const patch of patches) {
|
|
900
|
+
console.log(patch);
|
|
901
|
+
}
|
|
902
|
+
console.groupEnd();
|
|
899
903
|
}
|
|
900
904
|
}
|
|
901
905
|
|
|
@@ -1796,113 +1800,258 @@ class RemoteEngine {
|
|
|
1796
1800
|
}
|
|
1797
1801
|
}
|
|
1798
1802
|
|
|
1799
|
-
// src/
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1803
|
+
// src/resolver.ts
|
|
1804
|
+
class ComponentResolver {
|
|
1805
|
+
cache = new Map;
|
|
1806
|
+
options;
|
|
1807
|
+
moduleRegistry;
|
|
1808
|
+
constructor(options = {}) {
|
|
1809
|
+
this.options = {
|
|
1810
|
+
baseDir: options.baseDir || process.cwd(),
|
|
1811
|
+
cache: options.cache ?? true,
|
|
1812
|
+
customFetch: options.customFetch || this.defaultFetch.bind(this),
|
|
1813
|
+
moduleRegistry: options.moduleRegistry
|
|
1814
|
+
};
|
|
1815
|
+
this.moduleRegistry = options.moduleRegistry;
|
|
1809
1816
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1817
|
+
async resolve(importStmt) {
|
|
1818
|
+
if (this.moduleRegistry) {
|
|
1819
|
+
const names = importStmt.clause.type === "named" ? importStmt.clause.names : [importStmt.clause.name];
|
|
1820
|
+
const allFound = names.every((name) => this.moduleRegistry.has(name));
|
|
1821
|
+
if (allFound) {
|
|
1822
|
+
const result = {};
|
|
1823
|
+
for (const name of names) {
|
|
1824
|
+
const def = this.moduleRegistry.get(name);
|
|
1825
|
+
result[name] = {
|
|
1826
|
+
module: def,
|
|
1827
|
+
template: def?.template || ""
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
return result;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
const sourcePath = this.getSourcePath(importStmt.source);
|
|
1834
|
+
if (this.options.cache && this.cache.has(sourcePath)) {
|
|
1835
|
+
const cached = this.cache.get(sourcePath);
|
|
1836
|
+
return this.extractComponents(importStmt.clause, cached);
|
|
1837
|
+
}
|
|
1838
|
+
let component;
|
|
1839
|
+
if (importStmt.source.type === "local") {
|
|
1840
|
+
component = await this.resolveLocal(importStmt.source.path);
|
|
1841
|
+
} else {
|
|
1842
|
+
component = await this.resolveUrl(importStmt.source.url);
|
|
1843
|
+
}
|
|
1844
|
+
if (this.options.cache) {
|
|
1845
|
+
this.cache.set(sourcePath, component);
|
|
1846
|
+
}
|
|
1847
|
+
return this.extractComponents(importStmt.clause, component);
|
|
1814
1848
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1849
|
+
async resolveLocal(path) {
|
|
1850
|
+
const { resolve, join } = await import("path");
|
|
1851
|
+
const { readFile } = await import("fs/promises");
|
|
1852
|
+
const basePath = resolve(this.options.baseDir, path);
|
|
1853
|
+
const hypenPath = basePath.endsWith(".hypen") ? basePath : `${basePath}.hypen`;
|
|
1854
|
+
const template = await readFile(hypenPath, "utf-8");
|
|
1855
|
+
const modulePath = hypenPath.replace(/\.hypen$/, ".ts");
|
|
1856
|
+
let module = {};
|
|
1857
|
+
try {
|
|
1858
|
+
module = await import(modulePath);
|
|
1859
|
+
} catch {}
|
|
1860
|
+
return { module, template };
|
|
1825
1861
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1862
|
+
async resolveUrl(url) {
|
|
1863
|
+
try {
|
|
1864
|
+
const response = await this.options.customFetch(url);
|
|
1865
|
+
const data = JSON.parse(response);
|
|
1866
|
+
if (!data.module || !data.template) {
|
|
1867
|
+
throw new Error(`Invalid component format from ${url}. Expected { module, template }`);
|
|
1868
|
+
}
|
|
1869
|
+
return data;
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
throw new Error(`Failed to resolve component from ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1829
1872
|
}
|
|
1830
|
-
return this.wasmEngine;
|
|
1831
1873
|
}
|
|
1832
|
-
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
}
|
|
1874
|
+
async defaultFetch(url) {
|
|
1875
|
+
const response = await fetch(url);
|
|
1876
|
+
if (!response.ok) {
|
|
1877
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1878
|
+
}
|
|
1879
|
+
return response.text();
|
|
1837
1880
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1881
|
+
extractComponents(clause, component) {
|
|
1882
|
+
if (clause.type === "default") {
|
|
1883
|
+
return {
|
|
1884
|
+
[clause.name]: component
|
|
1885
|
+
};
|
|
1886
|
+
} else {
|
|
1887
|
+
const result = {};
|
|
1888
|
+
for (const name of clause.names) {
|
|
1889
|
+
result[name] = component;
|
|
1890
|
+
}
|
|
1842
1891
|
return result;
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
renderSource(source) {
|
|
1846
|
-
const engine = this.ensureInitialized();
|
|
1847
|
-
engine.renderSource(source);
|
|
1892
|
+
}
|
|
1848
1893
|
}
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
engine.renderLazyComponent(source);
|
|
1894
|
+
getSourcePath(source) {
|
|
1895
|
+
return source.type === "local" ? source.path : source.url;
|
|
1852
1896
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
engine.renderInto(source, parentNodeId, unwrapForWasm(state));
|
|
1897
|
+
clearCache() {
|
|
1898
|
+
this.cache.clear();
|
|
1856
1899
|
}
|
|
1857
|
-
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1900
|
+
static parseImports(text) {
|
|
1901
|
+
const imports = [];
|
|
1902
|
+
const importRegex = /import\s+(?:(\{[^}]*\})|(\w+))\s+from\s+["']([^"']+)["']/g;
|
|
1903
|
+
let match2;
|
|
1904
|
+
while ((match2 = importRegex.exec(text)) !== null) {
|
|
1905
|
+
const [, namedImports, defaultImport, source] = match2;
|
|
1906
|
+
if (!source)
|
|
1907
|
+
continue;
|
|
1908
|
+
let clause;
|
|
1909
|
+
if (namedImports) {
|
|
1910
|
+
const names = namedImports.slice(1, -1).split(",").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
1911
|
+
clause = { type: "named", names };
|
|
1912
|
+
} else if (defaultImport) {
|
|
1913
|
+
clause = { type: "default", name: defaultImport };
|
|
1914
|
+
} else {
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
const sourceObj = source.startsWith("http://") || source.startsWith("https://") ? { type: "url", url: source } : { type: "local", path: source };
|
|
1918
|
+
imports.push({ clause, source: sourceObj });
|
|
1861
1919
|
}
|
|
1862
|
-
|
|
1863
|
-
log9.debug("State changed (sparse):", paths);
|
|
1920
|
+
return imports;
|
|
1864
1921
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// src/components/builtin.ts
|
|
1925
|
+
init_app();
|
|
1926
|
+
init_logger();
|
|
1927
|
+
var log9 = frameworkLoggers.router;
|
|
1928
|
+
var Router = app.defineState({
|
|
1929
|
+
currentPath: "/",
|
|
1930
|
+
matchedRoute: null,
|
|
1931
|
+
routeParams: {}
|
|
1932
|
+
}, { name: "__Router" }).onCreated((state, context) => {
|
|
1933
|
+
if (!context) {
|
|
1934
|
+
log9.error("Requires global context");
|
|
1935
|
+
return;
|
|
1871
1936
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1937
|
+
const router = context.__router;
|
|
1938
|
+
if (!router) {
|
|
1939
|
+
log9.error("Router not found in context");
|
|
1940
|
+
return;
|
|
1875
1941
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1942
|
+
const hypenEngine = context.__hypenEngine;
|
|
1943
|
+
const updateRouteVisibility = async (currentPath) => {
|
|
1944
|
+
const routeElements = document.querySelectorAll('[data-hypen-type="route"]');
|
|
1945
|
+
let matchFound = false;
|
|
1946
|
+
for (let index = 0;index < routeElements.length; index++) {
|
|
1947
|
+
const routeEl = routeElements[index];
|
|
1948
|
+
const htmlEl = routeEl;
|
|
1949
|
+
const routePath = htmlEl.dataset.routePath || "/";
|
|
1950
|
+
const isMatch = routePath === currentPath;
|
|
1951
|
+
htmlEl.style.display = isMatch ? "flex" : "none";
|
|
1952
|
+
if (isMatch) {
|
|
1953
|
+
matchFound = true;
|
|
1954
|
+
const componentName = htmlEl.dataset.routeComponent;
|
|
1955
|
+
const isLazy = htmlEl.dataset.routeLazy === "true";
|
|
1956
|
+
const hasContent = htmlEl.children.length > 0;
|
|
1957
|
+
if (componentName && !hasContent && hypenEngine) {
|
|
1958
|
+
try {
|
|
1959
|
+
await hypenEngine.renderLazyRoute(routePath, componentName, htmlEl);
|
|
1960
|
+
} catch (err) {
|
|
1961
|
+
log9.error(`Failed to render route ${routePath}:`, err);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
if (!matchFound) {
|
|
1967
|
+
log9.warn(`No route matched path: ${currentPath}. Available routes:`, Array.from(routeElements).map((el) => el.dataset.routePath));
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
setTimeout(() => {
|
|
1971
|
+
updateRouteVisibility(state.currentPath);
|
|
1972
|
+
}, 100);
|
|
1973
|
+
router.onNavigate((routeState) => {
|
|
1974
|
+
state.currentPath = routeState.currentPath;
|
|
1975
|
+
state.routeParams = routeState.params;
|
|
1976
|
+
updateRouteVisibility(routeState.currentPath);
|
|
1977
|
+
});
|
|
1978
|
+
}).build();
|
|
1979
|
+
var Route = app.defineState({}, { name: "__Route" }).build();
|
|
1980
|
+
var Link = app.defineState({
|
|
1981
|
+
to: "/",
|
|
1982
|
+
isActive: false
|
|
1983
|
+
}, { name: "__Link" }).onAction("navigate", ({ state, context }) => {
|
|
1984
|
+
const router = context?.__router;
|
|
1985
|
+
if (!router) {
|
|
1986
|
+
log9.error("Link requires router context");
|
|
1987
|
+
return;
|
|
1879
1988
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1989
|
+
const targetPath = state.to;
|
|
1990
|
+
router.push(targetPath);
|
|
1991
|
+
}).onCreated((state, context) => {
|
|
1992
|
+
if (!context)
|
|
1993
|
+
return;
|
|
1994
|
+
const router = context.__router;
|
|
1995
|
+
if (router) {
|
|
1996
|
+
router.onNavigate((routeState) => {
|
|
1997
|
+
state.isActive = routeState.currentPath === state.to;
|
|
1884
1998
|
});
|
|
1885
1999
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
2000
|
+
}).build();
|
|
2001
|
+
|
|
2002
|
+
// src/index.ts
|
|
2003
|
+
init_app();
|
|
2004
|
+
|
|
2005
|
+
// src/hypen.ts
|
|
2006
|
+
function createBindingProxy(root) {
|
|
2007
|
+
const handler = {
|
|
2008
|
+
get(_, prop) {
|
|
2009
|
+
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
|
|
2010
|
+
return () => `\${${root}}`;
|
|
2011
|
+
}
|
|
2012
|
+
if (typeof prop === "symbol") {
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (prop === "toJSON") {
|
|
2016
|
+
return () => `\${${root}}`;
|
|
2017
|
+
}
|
|
2018
|
+
return createBindingProxy(`${root}.${prop}`);
|
|
2019
|
+
},
|
|
2020
|
+
has() {
|
|
2021
|
+
return true;
|
|
2022
|
+
},
|
|
2023
|
+
ownKeys() {
|
|
2024
|
+
return [];
|
|
2025
|
+
},
|
|
2026
|
+
getOwnPropertyDescriptor() {
|
|
2027
|
+
return {
|
|
2028
|
+
configurable: true,
|
|
2029
|
+
enumerable: true
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
return new Proxy({}, handler);
|
|
2034
|
+
}
|
|
2035
|
+
var state = createBindingProxy("state");
|
|
2036
|
+
var item = createBindingProxy("item");
|
|
2037
|
+
var index = {
|
|
2038
|
+
[Symbol.toPrimitive]: () => "${index}",
|
|
2039
|
+
toString: () => "${index}",
|
|
2040
|
+
valueOf: () => "${index}",
|
|
2041
|
+
toJSON: () => "${index}"
|
|
2042
|
+
};
|
|
2043
|
+
function hypen(strings, ...expressions) {
|
|
2044
|
+
let result = strings[0];
|
|
2045
|
+
for (let i = 0;i < expressions.length; i++) {
|
|
2046
|
+
const expr = expressions[i];
|
|
2047
|
+
result += String(expr);
|
|
2048
|
+
result += strings[i + 1];
|
|
1901
2049
|
}
|
|
2050
|
+
return result.trim();
|
|
1902
2051
|
}
|
|
1903
2052
|
|
|
1904
|
-
// src/
|
|
1905
|
-
|
|
2053
|
+
// src/index.ts
|
|
2054
|
+
init_state();
|
|
1906
2055
|
|
|
1907
2056
|
// src/remote/session.ts
|
|
1908
2057
|
class SessionManager {
|
|
@@ -2028,1108 +2177,11 @@ class SessionManager {
|
|
|
2028
2177
|
}
|
|
2029
2178
|
}
|
|
2030
2179
|
|
|
2031
|
-
// src/remote/server.ts
|
|
2032
|
-
init_logger();
|
|
2033
|
-
var log10 = frameworkLoggers.remote;
|
|
2034
|
-
|
|
2035
|
-
class RemoteServer {
|
|
2036
|
-
_module = null;
|
|
2037
|
-
_moduleName = "App";
|
|
2038
|
-
_ui = "";
|
|
2039
|
-
_config = {};
|
|
2040
|
-
_sessionConfig = {};
|
|
2041
|
-
_onConnectionCallbacks = [];
|
|
2042
|
-
_onDisconnectionCallbacks = [];
|
|
2043
|
-
clients = new Map;
|
|
2044
|
-
nextClientId = 1;
|
|
2045
|
-
server = null;
|
|
2046
|
-
sessionManager = null;
|
|
2047
|
-
module(name, module) {
|
|
2048
|
-
this._moduleName = name;
|
|
2049
|
-
this._module = module;
|
|
2050
|
-
return this;
|
|
2051
|
-
}
|
|
2052
|
-
ui(dsl) {
|
|
2053
|
-
this._ui = dsl;
|
|
2054
|
-
return this;
|
|
2055
|
-
}
|
|
2056
|
-
config(config2) {
|
|
2057
|
-
this._config = { ...this._config, ...config2 };
|
|
2058
|
-
return this;
|
|
2059
|
-
}
|
|
2060
|
-
session(config2) {
|
|
2061
|
-
this._sessionConfig = config2;
|
|
2062
|
-
return this;
|
|
2063
|
-
}
|
|
2064
|
-
onConnection(callback) {
|
|
2065
|
-
this._onConnectionCallbacks.push(callback);
|
|
2066
|
-
return this;
|
|
2067
|
-
}
|
|
2068
|
-
onDisconnection(callback) {
|
|
2069
|
-
this._onDisconnectionCallbacks.push(callback);
|
|
2070
|
-
return this;
|
|
2071
|
-
}
|
|
2072
|
-
listen(port) {
|
|
2073
|
-
if (!this._module) {
|
|
2074
|
-
throw new Error("Module not set. Call .module() before .listen()");
|
|
2075
|
-
}
|
|
2076
|
-
if (!this._ui) {
|
|
2077
|
-
throw new Error("UI not set. Call .ui() before .listen()");
|
|
2078
|
-
}
|
|
2079
|
-
this.sessionManager = new SessionManager(this._sessionConfig);
|
|
2080
|
-
const finalPort = port ?? this._config.port ?? 3000;
|
|
2081
|
-
const hostname = this._config.hostname ?? "0.0.0.0";
|
|
2082
|
-
this.server = Bun.serve({
|
|
2083
|
-
port: finalPort,
|
|
2084
|
-
hostname,
|
|
2085
|
-
websocket: {
|
|
2086
|
-
open: (ws) => this.handleOpen(ws),
|
|
2087
|
-
message: (ws, message) => this.handleMessage(ws, message),
|
|
2088
|
-
close: (ws) => this.handleClose(ws)
|
|
2089
|
-
},
|
|
2090
|
-
fetch: (req, server) => {
|
|
2091
|
-
const url = new URL(req.url);
|
|
2092
|
-
if (server.upgrade(req, { data: undefined })) {
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
if (url.pathname === "/health") {
|
|
2096
|
-
return new Response("OK", { status: 200 });
|
|
2097
|
-
}
|
|
2098
|
-
if (url.pathname === "/stats") {
|
|
2099
|
-
const stats = this.sessionManager?.getStats() ?? {
|
|
2100
|
-
activeSessions: 0,
|
|
2101
|
-
pendingSessions: 0,
|
|
2102
|
-
totalConnections: 0
|
|
2103
|
-
};
|
|
2104
|
-
return new Response(JSON.stringify(stats), {
|
|
2105
|
-
headers: { "Content-Type": "application/json" }
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
return new Response("Hypen Remote Server", { status: 200 });
|
|
2109
|
-
}
|
|
2110
|
-
});
|
|
2111
|
-
log10.info(`Hypen app streaming on ws://${hostname}:${finalPort}`);
|
|
2112
|
-
return this;
|
|
2113
|
-
}
|
|
2114
|
-
stop() {
|
|
2115
|
-
if (this.server) {
|
|
2116
|
-
this.server.stop();
|
|
2117
|
-
this.server = null;
|
|
2118
|
-
}
|
|
2119
|
-
if (this.sessionManager) {
|
|
2120
|
-
this.sessionManager.destroy();
|
|
2121
|
-
this.sessionManager = null;
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
get url() {
|
|
2125
|
-
if (!this.server)
|
|
2126
|
-
return null;
|
|
2127
|
-
const hostname = this._config.hostname ?? "localhost";
|
|
2128
|
-
const port = this._config.port ?? 3000;
|
|
2129
|
-
return `ws://${hostname}:${port}`;
|
|
2130
|
-
}
|
|
2131
|
-
async handleOpen(ws) {
|
|
2132
|
-
try {
|
|
2133
|
-
const clientId = `client_${this.nextClientId++}`;
|
|
2134
|
-
const connectedAt = new Date;
|
|
2135
|
-
const engine = new Engine;
|
|
2136
|
-
await engine.init();
|
|
2137
|
-
const moduleInstance = new HypenModuleInstance(engine, this._module);
|
|
2138
|
-
const clientData = {
|
|
2139
|
-
id: clientId,
|
|
2140
|
-
engine,
|
|
2141
|
-
moduleInstance,
|
|
2142
|
-
revision: 0,
|
|
2143
|
-
connectedAt,
|
|
2144
|
-
sessionId: "",
|
|
2145
|
-
helloReceived: false
|
|
2146
|
-
};
|
|
2147
|
-
this.clients.set(ws, clientData);
|
|
2148
|
-
clientData.helloTimeout = setTimeout(() => {
|
|
2149
|
-
if (!clientData.helloReceived) {
|
|
2150
|
-
this.initializeSession(ws, clientData, undefined, undefined);
|
|
2151
|
-
}
|
|
2152
|
-
}, 1000);
|
|
2153
|
-
} catch (error) {
|
|
2154
|
-
log10.error("Error handling WebSocket open:", error);
|
|
2155
|
-
ws.close(1011, "Internal server error");
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
async initializeSession(ws, clientData, requestedSessionId, props) {
|
|
2159
|
-
if (clientData.helloReceived)
|
|
2160
|
-
return;
|
|
2161
|
-
clientData.helloReceived = true;
|
|
2162
|
-
if (clientData.helloTimeout) {
|
|
2163
|
-
clearTimeout(clientData.helloTimeout);
|
|
2164
|
-
clientData.helloTimeout = undefined;
|
|
2165
|
-
}
|
|
2166
|
-
let session;
|
|
2167
|
-
let isNew = true;
|
|
2168
|
-
let isRestored = false;
|
|
2169
|
-
let restoredState = null;
|
|
2170
|
-
if (requestedSessionId && this.sessionManager) {
|
|
2171
|
-
const resumed = this.sessionManager.resumeSession(requestedSessionId);
|
|
2172
|
-
if (resumed) {
|
|
2173
|
-
session = resumed.session;
|
|
2174
|
-
restoredState = resumed.savedState;
|
|
2175
|
-
isNew = false;
|
|
2176
|
-
isRestored = true;
|
|
2177
|
-
await this.triggerReconnect(clientData, session, restoredState);
|
|
2178
|
-
} else {
|
|
2179
|
-
const activeSession = this.sessionManager.getActiveSession(requestedSessionId);
|
|
2180
|
-
if (activeSession) {
|
|
2181
|
-
const handled = await this.handleConcurrentConnection(ws, clientData, activeSession, props);
|
|
2182
|
-
if (!handled)
|
|
2183
|
-
return;
|
|
2184
|
-
session = activeSession;
|
|
2185
|
-
isNew = false;
|
|
2186
|
-
} else {
|
|
2187
|
-
session = this.sessionManager.createSession(props);
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
} else if (this.sessionManager) {
|
|
2191
|
-
session = this.sessionManager.createSession(props);
|
|
2192
|
-
} else {
|
|
2193
|
-
session = {
|
|
2194
|
-
id: crypto.randomUUID(),
|
|
2195
|
-
ttl: 3600,
|
|
2196
|
-
createdAt: new Date,
|
|
2197
|
-
lastConnectedAt: new Date,
|
|
2198
|
-
props
|
|
2199
|
-
};
|
|
2200
|
-
}
|
|
2201
|
-
clientData.sessionId = session.id;
|
|
2202
|
-
this.sessionManager?.trackConnection(session.id, ws);
|
|
2203
|
-
const sessionAck = {
|
|
2204
|
-
type: "sessionAck",
|
|
2205
|
-
sessionId: session.id,
|
|
2206
|
-
isNew,
|
|
2207
|
-
isRestored
|
|
2208
|
-
};
|
|
2209
|
-
ws.send(JSON.stringify(sessionAck));
|
|
2210
|
-
this.setupRenderCallback(ws, clientData);
|
|
2211
|
-
const initialPatches = [];
|
|
2212
|
-
clientData.engine.setRenderCallback((patches) => {
|
|
2213
|
-
initialPatches.push(...patches);
|
|
2214
|
-
});
|
|
2215
|
-
clientData.engine.renderSource(this._ui);
|
|
2216
|
-
this.setupRenderCallback(ws, clientData);
|
|
2217
|
-
const initialMessage = {
|
|
2218
|
-
type: "initialTree",
|
|
2219
|
-
module: this._moduleName,
|
|
2220
|
-
state: clientData.moduleInstance.getState(),
|
|
2221
|
-
patches: initialPatches,
|
|
2222
|
-
revision: 0
|
|
2223
|
-
};
|
|
2224
|
-
ws.send(JSON.stringify(initialMessage));
|
|
2225
|
-
const client = {
|
|
2226
|
-
id: clientData.id,
|
|
2227
|
-
socket: ws,
|
|
2228
|
-
connectedAt: clientData.connectedAt
|
|
2229
|
-
};
|
|
2230
|
-
this._onConnectionCallbacks.forEach((cb) => cb(client));
|
|
2231
|
-
}
|
|
2232
|
-
setupRenderCallback(ws, clientData) {
|
|
2233
|
-
clientData.engine.setRenderCallback((patches) => {
|
|
2234
|
-
const data = this.clients.get(ws);
|
|
2235
|
-
if (!data)
|
|
2236
|
-
return;
|
|
2237
|
-
data.revision++;
|
|
2238
|
-
const message = {
|
|
2239
|
-
type: "patch",
|
|
2240
|
-
module: this._moduleName,
|
|
2241
|
-
patches,
|
|
2242
|
-
revision: data.revision
|
|
2243
|
-
};
|
|
2244
|
-
ws.send(JSON.stringify(message));
|
|
2245
|
-
if (this.sessionManager?.getConcurrentPolicy() === "allow-multiple" && data.sessionId) {
|
|
2246
|
-
const connections = this.sessionManager.getConnections(data.sessionId);
|
|
2247
|
-
if (connections) {
|
|
2248
|
-
for (const conn of connections) {
|
|
2249
|
-
if (conn !== ws) {
|
|
2250
|
-
conn.send(JSON.stringify(message));
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
});
|
|
2256
|
-
}
|
|
2257
|
-
async handleConcurrentConnection(ws, clientData, existingSession, props) {
|
|
2258
|
-
const policy = this.sessionManager?.getConcurrentPolicy() ?? "kick-old";
|
|
2259
|
-
switch (policy) {
|
|
2260
|
-
case "kick-old": {
|
|
2261
|
-
const existingConnections = this.sessionManager?.getConnections(existingSession.id);
|
|
2262
|
-
if (existingConnections) {
|
|
2263
|
-
for (const conn of existingConnections) {
|
|
2264
|
-
const oldWs = conn;
|
|
2265
|
-
const expiredMsg = {
|
|
2266
|
-
type: "sessionExpired",
|
|
2267
|
-
sessionId: existingSession.id,
|
|
2268
|
-
reason: "kicked"
|
|
2269
|
-
};
|
|
2270
|
-
oldWs.send(JSON.stringify(expiredMsg));
|
|
2271
|
-
oldWs.close(1000, "Session taken by new connection");
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
return true;
|
|
2275
|
-
}
|
|
2276
|
-
case "reject-new": {
|
|
2277
|
-
const expiredMsg = {
|
|
2278
|
-
type: "sessionExpired",
|
|
2279
|
-
sessionId: existingSession.id,
|
|
2280
|
-
reason: "kicked"
|
|
2281
|
-
};
|
|
2282
|
-
ws.send(JSON.stringify(expiredMsg));
|
|
2283
|
-
ws.close(1000, "Session already active");
|
|
2284
|
-
return false;
|
|
2285
|
-
}
|
|
2286
|
-
case "allow-multiple": {
|
|
2287
|
-
return true;
|
|
2288
|
-
}
|
|
2289
|
-
default:
|
|
2290
|
-
return true;
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
async triggerReconnect(clientData, session, savedState) {
|
|
2294
|
-
const handler = this._module?.handlers.onReconnect;
|
|
2295
|
-
if (!handler)
|
|
2296
|
-
return;
|
|
2297
|
-
let restored = false;
|
|
2298
|
-
const restore = (state) => {
|
|
2299
|
-
restored = true;
|
|
2300
|
-
clientData.moduleInstance.updateState(state);
|
|
2301
|
-
};
|
|
2302
|
-
await handler({ session, restore });
|
|
2303
|
-
}
|
|
2304
|
-
handleMessage(ws, message) {
|
|
2305
|
-
try {
|
|
2306
|
-
const clientData = this.clients.get(ws);
|
|
2307
|
-
if (!clientData)
|
|
2308
|
-
return;
|
|
2309
|
-
const msg = JSON.parse(message.toString());
|
|
2310
|
-
switch (msg.type) {
|
|
2311
|
-
case "hello": {
|
|
2312
|
-
const helloMsg = msg;
|
|
2313
|
-
this.initializeSession(ws, clientData, helloMsg.sessionId, helloMsg.props);
|
|
2314
|
-
break;
|
|
2315
|
-
}
|
|
2316
|
-
case "dispatchAction": {
|
|
2317
|
-
const actionMsg = msg;
|
|
2318
|
-
clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
|
|
2319
|
-
break;
|
|
2320
|
-
}
|
|
2321
|
-
default:
|
|
2322
|
-
break;
|
|
2323
|
-
}
|
|
2324
|
-
} catch (error) {
|
|
2325
|
-
log10.error("Error handling WebSocket message:", error);
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
async handleClose(ws) {
|
|
2329
|
-
const clientData = this.clients.get(ws);
|
|
2330
|
-
if (!clientData)
|
|
2331
|
-
return;
|
|
2332
|
-
if (clientData.helloTimeout) {
|
|
2333
|
-
clearTimeout(clientData.helloTimeout);
|
|
2334
|
-
}
|
|
2335
|
-
const currentState = clientData.moduleInstance.getState();
|
|
2336
|
-
if (clientData.sessionId && this._module?.handlers.onDisconnect) {
|
|
2337
|
-
const session = this.sessionManager?.getActiveSession(clientData.sessionId);
|
|
2338
|
-
if (session) {
|
|
2339
|
-
await this._module.handlers.onDisconnect({
|
|
2340
|
-
state: currentState,
|
|
2341
|
-
session
|
|
2342
|
-
});
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
if (clientData.sessionId && this.sessionManager) {
|
|
2346
|
-
this.sessionManager.untrackConnection(clientData.sessionId, ws);
|
|
2347
|
-
if (this.sessionManager.getConnectionCount(clientData.sessionId) === 0) {
|
|
2348
|
-
const session = this.sessionManager.getActiveSession(clientData.sessionId);
|
|
2349
|
-
if (session) {
|
|
2350
|
-
this.sessionManager.suspendSession(clientData.sessionId, currentState, async (expiredSession) => {
|
|
2351
|
-
if (this._module?.handlers.onExpire) {
|
|
2352
|
-
await this._module.handlers.onExpire({ session: expiredSession });
|
|
2353
|
-
}
|
|
2354
|
-
});
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
await clientData.moduleInstance.destroy();
|
|
2359
|
-
this.clients.delete(ws);
|
|
2360
|
-
const client = {
|
|
2361
|
-
id: clientData.id,
|
|
2362
|
-
socket: ws,
|
|
2363
|
-
connectedAt: clientData.connectedAt
|
|
2364
|
-
};
|
|
2365
|
-
this._onDisconnectionCallbacks.forEach((cb) => cb(client));
|
|
2366
|
-
}
|
|
2367
|
-
getClientCount() {
|
|
2368
|
-
return this.clients.size;
|
|
2369
|
-
}
|
|
2370
|
-
getSessionStats() {
|
|
2371
|
-
return this.sessionManager?.getStats() ?? {
|
|
2372
|
-
activeSessions: 0,
|
|
2373
|
-
pendingSessions: 0,
|
|
2374
|
-
totalConnections: 0
|
|
2375
|
-
};
|
|
2376
|
-
}
|
|
2377
|
-
broadcast(message) {
|
|
2378
|
-
const json = JSON.stringify(message);
|
|
2379
|
-
this.clients.forEach((_, ws) => {
|
|
2380
|
-
ws.send(json);
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
function serve(options) {
|
|
2385
|
-
const server = new RemoteServer().module(options.moduleName ?? "App", options.module).ui(options.ui);
|
|
2386
|
-
if (options.port || options.hostname) {
|
|
2387
|
-
server.config({
|
|
2388
|
-
port: options.port,
|
|
2389
|
-
hostname: options.hostname
|
|
2390
|
-
});
|
|
2391
|
-
}
|
|
2392
|
-
if (options.session) {
|
|
2393
|
-
server.session(options.session);
|
|
2394
|
-
}
|
|
2395
|
-
if (options.onConnection) {
|
|
2396
|
-
server.onConnection(options.onConnection);
|
|
2397
|
-
}
|
|
2398
|
-
if (options.onDisconnection) {
|
|
2399
|
-
server.onDisconnection(options.onDisconnection);
|
|
2400
|
-
}
|
|
2401
|
-
return server.listen(options.port);
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
|
-
// src/loader.ts
|
|
2405
|
-
init_logger();
|
|
2406
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
2407
|
-
import { join } from "path";
|
|
2408
|
-
var log11 = frameworkLoggers.loader;
|
|
2409
|
-
|
|
2410
|
-
class ComponentLoader {
|
|
2411
|
-
components = new Map;
|
|
2412
|
-
register(name, module, template, path) {
|
|
2413
|
-
this.components.set(name, {
|
|
2414
|
-
name,
|
|
2415
|
-
module,
|
|
2416
|
-
template,
|
|
2417
|
-
path: path || name
|
|
2418
|
-
});
|
|
2419
|
-
}
|
|
2420
|
-
get(name) {
|
|
2421
|
-
return this.components.get(name);
|
|
2422
|
-
}
|
|
2423
|
-
has(name) {
|
|
2424
|
-
return this.components.has(name);
|
|
2425
|
-
}
|
|
2426
|
-
getNames() {
|
|
2427
|
-
return Array.from(this.components.keys());
|
|
2428
|
-
}
|
|
2429
|
-
getAll() {
|
|
2430
|
-
return Array.from(this.components.values());
|
|
2431
|
-
}
|
|
2432
|
-
clear() {
|
|
2433
|
-
this.components.clear();
|
|
2434
|
-
}
|
|
2435
|
-
async loadFromDirectory(name, dirPath) {
|
|
2436
|
-
try {
|
|
2437
|
-
const modulePath = join(dirPath, "component.ts");
|
|
2438
|
-
const moduleExport = await import(modulePath);
|
|
2439
|
-
const module = moduleExport.default;
|
|
2440
|
-
const templatePath = join(dirPath, "component.hypen");
|
|
2441
|
-
const template = readFileSync(templatePath, "utf-8");
|
|
2442
|
-
this.register(name, module, template, dirPath);
|
|
2443
|
-
log11.debug(`Loaded component: ${name} from ${dirPath}`);
|
|
2444
|
-
} catch (error) {
|
|
2445
|
-
log11.error(`Failed to load component ${name} from ${dirPath}:`, error);
|
|
2446
|
-
throw error;
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
async loadFromComponentsDir(baseDir) {
|
|
2450
|
-
try {
|
|
2451
|
-
if (!existsSync(baseDir)) {
|
|
2452
|
-
log11.warn(`Components directory not found: ${baseDir}`);
|
|
2453
|
-
return;
|
|
2454
|
-
}
|
|
2455
|
-
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
2456
|
-
for (const entry of entries) {
|
|
2457
|
-
if (!entry.isDirectory())
|
|
2458
|
-
continue;
|
|
2459
|
-
const componentDir = join(baseDir, entry.name);
|
|
2460
|
-
const hypenPath = join(componentDir, "component.hypen");
|
|
2461
|
-
if (existsSync(hypenPath)) {
|
|
2462
|
-
await this.loadFromDirectory(entry.name, componentDir);
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
log11.debug(`Loaded ${this.components.size} components from ${baseDir}`);
|
|
2466
|
-
} catch (error) {
|
|
2467
|
-
log11.error(`Failed to load components from ${baseDir}:`, error);
|
|
2468
|
-
throw error;
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
var componentLoader = new ComponentLoader;
|
|
2473
|
-
|
|
2474
|
-
// src/resolver.ts
|
|
2475
|
-
class ComponentResolver {
|
|
2476
|
-
cache = new Map;
|
|
2477
|
-
options;
|
|
2478
|
-
moduleRegistry;
|
|
2479
|
-
constructor(options = {}) {
|
|
2480
|
-
this.options = {
|
|
2481
|
-
baseDir: options.baseDir || process.cwd(),
|
|
2482
|
-
cache: options.cache ?? true,
|
|
2483
|
-
customFetch: options.customFetch || this.defaultFetch.bind(this),
|
|
2484
|
-
moduleRegistry: options.moduleRegistry
|
|
2485
|
-
};
|
|
2486
|
-
this.moduleRegistry = options.moduleRegistry;
|
|
2487
|
-
}
|
|
2488
|
-
async resolve(importStmt) {
|
|
2489
|
-
if (this.moduleRegistry) {
|
|
2490
|
-
const names = importStmt.clause.type === "named" ? importStmt.clause.names : [importStmt.clause.name];
|
|
2491
|
-
const allFound = names.every((name) => this.moduleRegistry.has(name));
|
|
2492
|
-
if (allFound) {
|
|
2493
|
-
const result = {};
|
|
2494
|
-
for (const name of names) {
|
|
2495
|
-
const def = this.moduleRegistry.get(name);
|
|
2496
|
-
result[name] = {
|
|
2497
|
-
module: def,
|
|
2498
|
-
template: def?.template || ""
|
|
2499
|
-
};
|
|
2500
|
-
}
|
|
2501
|
-
return result;
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
const sourcePath = this.getSourcePath(importStmt.source);
|
|
2505
|
-
if (this.options.cache && this.cache.has(sourcePath)) {
|
|
2506
|
-
const cached = this.cache.get(sourcePath);
|
|
2507
|
-
return this.extractComponents(importStmt.clause, cached);
|
|
2508
|
-
}
|
|
2509
|
-
let component;
|
|
2510
|
-
if (importStmt.source.type === "local") {
|
|
2511
|
-
component = await this.resolveLocal(importStmt.source.path);
|
|
2512
|
-
} else {
|
|
2513
|
-
component = await this.resolveUrl(importStmt.source.url);
|
|
2514
|
-
}
|
|
2515
|
-
if (this.options.cache) {
|
|
2516
|
-
this.cache.set(sourcePath, component);
|
|
2517
|
-
}
|
|
2518
|
-
return this.extractComponents(importStmt.clause, component);
|
|
2519
|
-
}
|
|
2520
|
-
async resolveLocal(path) {
|
|
2521
|
-
const { resolve, join: join2 } = await import("path");
|
|
2522
|
-
const { readFile } = await import("fs/promises");
|
|
2523
|
-
const basePath = resolve(this.options.baseDir, path);
|
|
2524
|
-
const hypenPath = basePath.endsWith(".hypen") ? basePath : `${basePath}.hypen`;
|
|
2525
|
-
const template = await readFile(hypenPath, "utf-8");
|
|
2526
|
-
const modulePath = hypenPath.replace(/\.hypen$/, ".ts");
|
|
2527
|
-
let module = {};
|
|
2528
|
-
try {
|
|
2529
|
-
module = await import(modulePath);
|
|
2530
|
-
} catch {}
|
|
2531
|
-
return { module, template };
|
|
2532
|
-
}
|
|
2533
|
-
async resolveUrl(url) {
|
|
2534
|
-
try {
|
|
2535
|
-
const response = await this.options.customFetch(url);
|
|
2536
|
-
const data = JSON.parse(response);
|
|
2537
|
-
if (!data.module || !data.template) {
|
|
2538
|
-
throw new Error(`Invalid component format from ${url}. Expected { module, template }`);
|
|
2539
|
-
}
|
|
2540
|
-
return data;
|
|
2541
|
-
} catch (error) {
|
|
2542
|
-
throw new Error(`Failed to resolve component from ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
async defaultFetch(url) {
|
|
2546
|
-
const response = await fetch(url);
|
|
2547
|
-
if (!response.ok) {
|
|
2548
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2549
|
-
}
|
|
2550
|
-
return response.text();
|
|
2551
|
-
}
|
|
2552
|
-
extractComponents(clause, component) {
|
|
2553
|
-
if (clause.type === "default") {
|
|
2554
|
-
return {
|
|
2555
|
-
[clause.name]: component
|
|
2556
|
-
};
|
|
2557
|
-
} else {
|
|
2558
|
-
const result = {};
|
|
2559
|
-
for (const name of clause.names) {
|
|
2560
|
-
result[name] = component;
|
|
2561
|
-
}
|
|
2562
|
-
return result;
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
getSourcePath(source) {
|
|
2566
|
-
return source.type === "local" ? source.path : source.url;
|
|
2567
|
-
}
|
|
2568
|
-
clearCache() {
|
|
2569
|
-
this.cache.clear();
|
|
2570
|
-
}
|
|
2571
|
-
static parseImports(text) {
|
|
2572
|
-
const imports = [];
|
|
2573
|
-
const importRegex = /import\s+(?:(\{[^}]*\})|(\w+))\s+from\s+["']([^"']+)["']/g;
|
|
2574
|
-
let match2;
|
|
2575
|
-
while ((match2 = importRegex.exec(text)) !== null) {
|
|
2576
|
-
const [, namedImports, defaultImport, source] = match2;
|
|
2577
|
-
if (!source)
|
|
2578
|
-
continue;
|
|
2579
|
-
let clause;
|
|
2580
|
-
if (namedImports) {
|
|
2581
|
-
const names = namedImports.slice(1, -1).split(",").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
2582
|
-
clause = { type: "named", names };
|
|
2583
|
-
} else if (defaultImport) {
|
|
2584
|
-
clause = { type: "default", name: defaultImport };
|
|
2585
|
-
} else {
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2588
|
-
const sourceObj = source.startsWith("http://") || source.startsWith("https://") ? { type: "url", url: source } : { type: "local", path: source };
|
|
2589
|
-
imports.push({ clause, source: sourceObj });
|
|
2590
|
-
}
|
|
2591
|
-
return imports;
|
|
2592
|
-
}
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
// src/discovery.ts
|
|
2596
|
-
init_logger();
|
|
2597
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, watch } from "fs";
|
|
2598
|
-
import { join as join2, basename, resolve } from "path";
|
|
2599
|
-
async function discoverComponents(baseDir, options = {}) {
|
|
2600
|
-
const {
|
|
2601
|
-
patterns = ["single-file", "folder", "sibling", "index"],
|
|
2602
|
-
recursive = false,
|
|
2603
|
-
debug = false
|
|
2604
|
-
} = options;
|
|
2605
|
-
const log12 = debug ? (...args) => frameworkLoggers.discovery.debug(...args) : () => {};
|
|
2606
|
-
const resolvedDir = resolve(baseDir);
|
|
2607
|
-
const components = [];
|
|
2608
|
-
const seen = new Set;
|
|
2609
|
-
log12("Scanning directory:", resolvedDir);
|
|
2610
|
-
log12("Patterns:", patterns);
|
|
2611
|
-
const addTwoFileComponent = (name, hypenPath, modulePath) => {
|
|
2612
|
-
if (seen.has(name)) {
|
|
2613
|
-
log12(`Skipping duplicate: ${name}`);
|
|
2614
|
-
return;
|
|
2615
|
-
}
|
|
2616
|
-
seen.add(name);
|
|
2617
|
-
const templateRaw = readFileSync2(hypenPath, "utf-8");
|
|
2618
|
-
const template = templateRaw.trim();
|
|
2619
|
-
components.push({
|
|
2620
|
-
name,
|
|
2621
|
-
hypenPath,
|
|
2622
|
-
modulePath,
|
|
2623
|
-
template,
|
|
2624
|
-
hasModule: modulePath !== null,
|
|
2625
|
-
isSingleFile: false
|
|
2626
|
-
});
|
|
2627
|
-
log12(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
|
|
2628
|
-
};
|
|
2629
|
-
const addSingleFileComponent = (name, modulePath, template) => {
|
|
2630
|
-
if (seen.has(name)) {
|
|
2631
|
-
log12(`Skipping duplicate: ${name}`);
|
|
2632
|
-
return;
|
|
2633
|
-
}
|
|
2634
|
-
seen.add(name);
|
|
2635
|
-
components.push({
|
|
2636
|
-
name,
|
|
2637
|
-
hypenPath: null,
|
|
2638
|
-
modulePath,
|
|
2639
|
-
template,
|
|
2640
|
-
hasModule: true,
|
|
2641
|
-
isSingleFile: true
|
|
2642
|
-
});
|
|
2643
|
-
log12(`Found: ${name} (single-file with inline template)`);
|
|
2644
|
-
};
|
|
2645
|
-
const scanForFolderComponents = (dir) => {
|
|
2646
|
-
if (!existsSync2(dir))
|
|
2647
|
-
return;
|
|
2648
|
-
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2649
|
-
for (const entry of entries) {
|
|
2650
|
-
if (!entry.isDirectory())
|
|
2651
|
-
continue;
|
|
2652
|
-
const folderPath = join2(dir, entry.name);
|
|
2653
|
-
const componentName = entry.name;
|
|
2654
|
-
if (patterns.includes("folder")) {
|
|
2655
|
-
const hypenPath = join2(folderPath, "component.hypen");
|
|
2656
|
-
if (existsSync2(hypenPath)) {
|
|
2657
|
-
const modulePath = join2(folderPath, "component.ts");
|
|
2658
|
-
addTwoFileComponent(componentName, hypenPath, existsSync2(modulePath) ? modulePath : null);
|
|
2659
|
-
continue;
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
if (patterns.includes("index")) {
|
|
2663
|
-
const hypenPath = join2(folderPath, "index.hypen");
|
|
2664
|
-
if (existsSync2(hypenPath)) {
|
|
2665
|
-
const modulePath = join2(folderPath, "index.ts");
|
|
2666
|
-
addTwoFileComponent(componentName, hypenPath, existsSync2(modulePath) ? modulePath : null);
|
|
2667
|
-
continue;
|
|
2668
|
-
}
|
|
2669
|
-
}
|
|
2670
|
-
if (recursive) {
|
|
2671
|
-
scanForFolderComponents(folderPath);
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
};
|
|
2675
|
-
const scanForSiblingComponents = (dir) => {
|
|
2676
|
-
if (!existsSync2(dir))
|
|
2677
|
-
return;
|
|
2678
|
-
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2679
|
-
for (const entry of entries) {
|
|
2680
|
-
if (entry.isDirectory()) {
|
|
2681
|
-
if (recursive) {
|
|
2682
|
-
scanForSiblingComponents(join2(dir, entry.name));
|
|
2683
|
-
}
|
|
2684
|
-
continue;
|
|
2685
|
-
}
|
|
2686
|
-
if (!entry.name.endsWith(".hypen"))
|
|
2687
|
-
continue;
|
|
2688
|
-
const hypenPath = join2(dir, entry.name);
|
|
2689
|
-
const baseName = basename(entry.name, ".hypen");
|
|
2690
|
-
if (baseName === "component" || baseName === "index")
|
|
2691
|
-
continue;
|
|
2692
|
-
const modulePath = join2(dir, `${baseName}.ts`);
|
|
2693
|
-
addTwoFileComponent(baseName, hypenPath, existsSync2(modulePath) ? modulePath : null);
|
|
2694
|
-
}
|
|
2695
|
-
};
|
|
2696
|
-
const scanForSingleFileComponents = async (dir) => {
|
|
2697
|
-
if (!existsSync2(dir))
|
|
2698
|
-
return;
|
|
2699
|
-
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
2700
|
-
for (const entry of entries) {
|
|
2701
|
-
if (entry.isDirectory()) {
|
|
2702
|
-
if (recursive) {
|
|
2703
|
-
await scanForSingleFileComponents(join2(dir, entry.name));
|
|
2704
|
-
}
|
|
2705
|
-
continue;
|
|
2706
|
-
}
|
|
2707
|
-
if (!entry.name.endsWith(".ts"))
|
|
2708
|
-
continue;
|
|
2709
|
-
if (entry.name.startsWith(".") || entry.name.includes(".test.") || entry.name.includes(".spec."))
|
|
2710
|
-
continue;
|
|
2711
|
-
const baseName = basename(entry.name, ".ts");
|
|
2712
|
-
if (baseName === "component" || baseName === "index")
|
|
2713
|
-
continue;
|
|
2714
|
-
const hypenPath = join2(dir, `${baseName}.hypen`);
|
|
2715
|
-
if (existsSync2(hypenPath))
|
|
2716
|
-
continue;
|
|
2717
|
-
const modulePath = join2(dir, entry.name);
|
|
2718
|
-
const content = readFileSync2(modulePath, "utf-8");
|
|
2719
|
-
if (content.includes(".ui(") || content.includes(".ui(hypen")) {
|
|
2720
|
-
try {
|
|
2721
|
-
const moduleExport = await import(modulePath);
|
|
2722
|
-
const module = moduleExport.default;
|
|
2723
|
-
if (module && typeof module === "object" && module.template) {
|
|
2724
|
-
addSingleFileComponent(baseName, modulePath, module.template);
|
|
2725
|
-
}
|
|
2726
|
-
} catch (e) {
|
|
2727
|
-
log12(`Failed to import potential single-file component: ${entry.name}`, e);
|
|
2728
|
-
}
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
};
|
|
2732
|
-
if (patterns.includes("folder") || patterns.includes("index")) {
|
|
2733
|
-
scanForFolderComponents(resolvedDir);
|
|
2734
|
-
}
|
|
2735
|
-
if (patterns.includes("sibling")) {
|
|
2736
|
-
scanForSiblingComponents(resolvedDir);
|
|
2737
|
-
}
|
|
2738
|
-
if (patterns.includes("single-file")) {
|
|
2739
|
-
await scanForSingleFileComponents(resolvedDir);
|
|
2740
|
-
}
|
|
2741
|
-
log12(`Discovered ${components.length} components`);
|
|
2742
|
-
return components;
|
|
2743
|
-
}
|
|
2744
|
-
async function loadDiscoveredComponents(components) {
|
|
2745
|
-
const { app: app2 } = await Promise.resolve().then(() => (init_app(), exports_app));
|
|
2746
|
-
const loaded = new Map;
|
|
2747
|
-
for (const component of components) {
|
|
2748
|
-
let module;
|
|
2749
|
-
let template = component.template;
|
|
2750
|
-
if (component.modulePath) {
|
|
2751
|
-
const moduleExport = await import(component.modulePath);
|
|
2752
|
-
module = moduleExport.default;
|
|
2753
|
-
if (component.isSingleFile && module.template) {
|
|
2754
|
-
template = module.template;
|
|
2755
|
-
}
|
|
2756
|
-
} else {
|
|
2757
|
-
module = app2.defineState({}).build();
|
|
2758
|
-
}
|
|
2759
|
-
loaded.set(component.name, {
|
|
2760
|
-
name: component.name,
|
|
2761
|
-
module,
|
|
2762
|
-
template
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
return loaded;
|
|
2766
|
-
}
|
|
2767
|
-
function watchComponents(baseDir, options = {}) {
|
|
2768
|
-
const resolvedDir = resolve(baseDir);
|
|
2769
|
-
const {
|
|
2770
|
-
onChange,
|
|
2771
|
-
onAdd,
|
|
2772
|
-
onRemove,
|
|
2773
|
-
onUpdate,
|
|
2774
|
-
debug = false,
|
|
2775
|
-
...discoveryOptions
|
|
2776
|
-
} = options;
|
|
2777
|
-
const log12 = debug ? (...args) => frameworkLoggers.discovery.debug("[watch]", ...args) : () => {};
|
|
2778
|
-
let currentComponents = new Map;
|
|
2779
|
-
let debounceTimer = null;
|
|
2780
|
-
const initialScan = async () => {
|
|
2781
|
-
const components = await discoverComponents(resolvedDir, discoveryOptions);
|
|
2782
|
-
currentComponents = new Map(components.map((c) => [c.name, c]));
|
|
2783
|
-
onChange?.(components);
|
|
2784
|
-
};
|
|
2785
|
-
const rescan = async () => {
|
|
2786
|
-
if (debounceTimer) {
|
|
2787
|
-
clearTimeout(debounceTimer);
|
|
2788
|
-
}
|
|
2789
|
-
debounceTimer = setTimeout(async () => {
|
|
2790
|
-
log12("Rescanning...");
|
|
2791
|
-
const newComponents = await discoverComponents(resolvedDir, discoveryOptions);
|
|
2792
|
-
const newMap = new Map(newComponents.map((c) => [c.name, c]));
|
|
2793
|
-
for (const [name, component] of newMap) {
|
|
2794
|
-
const existing = currentComponents.get(name);
|
|
2795
|
-
if (!existing) {
|
|
2796
|
-
log12("Added:", name);
|
|
2797
|
-
onAdd?.(component);
|
|
2798
|
-
} else if (existing.template !== component.template || existing.modulePath !== component.modulePath) {
|
|
2799
|
-
log12("Updated:", name);
|
|
2800
|
-
onUpdate?.(component);
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
for (const name of currentComponents.keys()) {
|
|
2804
|
-
if (!newMap.has(name)) {
|
|
2805
|
-
log12("Removed:", name);
|
|
2806
|
-
onRemove?.(name);
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
currentComponents = newMap;
|
|
2810
|
-
onChange?.(newComponents);
|
|
2811
|
-
}, 100);
|
|
2812
|
-
};
|
|
2813
|
-
const watcher = watch(resolvedDir, { recursive: true }, (event, filename) => {
|
|
2814
|
-
if (!filename)
|
|
2815
|
-
return;
|
|
2816
|
-
if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
|
|
2817
|
-
log12("File changed:", filename);
|
|
2818
|
-
rescan();
|
|
2819
|
-
}
|
|
2820
|
-
});
|
|
2821
|
-
initialScan();
|
|
2822
|
-
return {
|
|
2823
|
-
stop: () => {
|
|
2824
|
-
watcher.close();
|
|
2825
|
-
if (debounceTimer) {
|
|
2826
|
-
clearTimeout(debounceTimer);
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
};
|
|
2830
|
-
}
|
|
2831
|
-
async function generateComponentsCode(baseDir, options = {}) {
|
|
2832
|
-
const components = await discoverComponents(baseDir, options);
|
|
2833
|
-
const resolvedDir = resolve(baseDir);
|
|
2834
|
-
let code = `/**
|
|
2835
|
-
* Auto-generated component imports
|
|
2836
|
-
* Generated by @hypen-space/core discovery
|
|
2837
|
-
*/
|
|
2838
|
-
|
|
2839
|
-
`;
|
|
2840
|
-
for (const component of components) {
|
|
2841
|
-
const relativePath = component.modulePath ? "./" + component.modulePath.replace(resolvedDir + "/", "").replace(/\.ts$/, ".js") : null;
|
|
2842
|
-
if (relativePath) {
|
|
2843
|
-
code += `import ${component.name}Module from "${relativePath}";
|
|
2844
|
-
`;
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
code += `
|
|
2848
|
-
import { app } from "@hypen-space/core";
|
|
2849
|
-
|
|
2850
|
-
`;
|
|
2851
|
-
for (const component of components) {
|
|
2852
|
-
if (component.isSingleFile) {
|
|
2853
|
-
code += `export const ${component.name} = {
|
|
2854
|
-
module: ${component.name}Module,
|
|
2855
|
-
template: ${component.name}Module.template,
|
|
2856
|
-
};
|
|
2857
|
-
|
|
2858
|
-
`;
|
|
2859
|
-
} else if (component.hasModule) {
|
|
2860
|
-
const templateJson = JSON.stringify(component.template);
|
|
2861
|
-
code += `export const ${component.name} = {
|
|
2862
|
-
module: ${component.name}Module,
|
|
2863
|
-
template: ${templateJson},
|
|
2864
|
-
};
|
|
2865
|
-
|
|
2866
|
-
`;
|
|
2867
|
-
} else {
|
|
2868
|
-
const templateJson = JSON.stringify(component.template);
|
|
2869
|
-
code += `const ${component.name}Module = app.defineState({}).build();
|
|
2870
|
-
export const ${component.name} = {
|
|
2871
|
-
module: ${component.name}Module,
|
|
2872
|
-
template: ${templateJson},
|
|
2873
|
-
};
|
|
2874
|
-
|
|
2875
|
-
`;
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
return code;
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
// src/plugin.ts
|
|
2882
|
-
init_logger();
|
|
2883
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
2884
|
-
import { dirname, basename as basename2, join as join3, resolve as resolve2 } from "path";
|
|
2885
|
-
function findModulePath(hypenPath, patterns) {
|
|
2886
|
-
const dir = dirname(hypenPath);
|
|
2887
|
-
const baseName = basename2(hypenPath, ".hypen");
|
|
2888
|
-
const parentDir = dirname(dir);
|
|
2889
|
-
const folderName = basename2(dir);
|
|
2890
|
-
for (const pattern of patterns) {
|
|
2891
|
-
let candidatePath = null;
|
|
2892
|
-
switch (pattern) {
|
|
2893
|
-
case "sibling":
|
|
2894
|
-
candidatePath = join3(dir, `${baseName}.ts`);
|
|
2895
|
-
break;
|
|
2896
|
-
case "component":
|
|
2897
|
-
if (baseName === "component") {
|
|
2898
|
-
candidatePath = join3(dir, "component.ts");
|
|
2899
|
-
}
|
|
2900
|
-
break;
|
|
2901
|
-
case "index":
|
|
2902
|
-
if (baseName === "index") {
|
|
2903
|
-
candidatePath = join3(dir, "index.ts");
|
|
2904
|
-
}
|
|
2905
|
-
break;
|
|
2906
|
-
}
|
|
2907
|
-
if (candidatePath && existsSync3(candidatePath)) {
|
|
2908
|
-
return candidatePath;
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
return null;
|
|
2912
|
-
}
|
|
2913
|
-
function getComponentName(hypenPath) {
|
|
2914
|
-
const baseName = basename2(hypenPath, ".hypen");
|
|
2915
|
-
if (baseName === "component" || baseName === "index") {
|
|
2916
|
-
return basename2(dirname(hypenPath));
|
|
2917
|
-
}
|
|
2918
|
-
return baseName;
|
|
2919
|
-
}
|
|
2920
|
-
function parseImports(text) {
|
|
2921
|
-
const imports = [];
|
|
2922
|
-
const importRegex = /import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+["']([^"']+)["']/g;
|
|
2923
|
-
let match2;
|
|
2924
|
-
while ((match2 = importRegex.exec(text)) !== null) {
|
|
2925
|
-
const [, namedImports, defaultImport, source] = match2;
|
|
2926
|
-
if (!source)
|
|
2927
|
-
continue;
|
|
2928
|
-
let names;
|
|
2929
|
-
if (namedImports) {
|
|
2930
|
-
names = namedImports.split(",").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
2931
|
-
} else if (defaultImport) {
|
|
2932
|
-
names = [defaultImport];
|
|
2933
|
-
} else {
|
|
2934
|
-
continue;
|
|
2935
|
-
}
|
|
2936
|
-
imports.push({ names, source });
|
|
2937
|
-
}
|
|
2938
|
-
return imports;
|
|
2939
|
-
}
|
|
2940
|
-
function removeImports(text) {
|
|
2941
|
-
return text.replace(/import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g, "");
|
|
2942
|
-
}
|
|
2943
|
-
function hypenPlugin(options = {}) {
|
|
2944
|
-
const { debug = false, patterns = ["sibling", "component", "index"] } = options;
|
|
2945
|
-
const log12 = debug ? (...args) => frameworkLoggers.plugin.debug(...args) : () => {};
|
|
2946
|
-
return {
|
|
2947
|
-
name: "hypen-loader",
|
|
2948
|
-
async setup(build) {
|
|
2949
|
-
build.onLoad({ filter: /\.hypen$/ }, async (args) => {
|
|
2950
|
-
const hypenPath = resolve2(args.path);
|
|
2951
|
-
log12("Loading:", hypenPath);
|
|
2952
|
-
const templateRaw = readFileSync3(hypenPath, "utf-8");
|
|
2953
|
-
const imports = parseImports(templateRaw);
|
|
2954
|
-
const template = removeImports(templateRaw).trim();
|
|
2955
|
-
if (imports.length > 0) {
|
|
2956
|
-
log12("Found imports:", imports);
|
|
2957
|
-
}
|
|
2958
|
-
const componentName = getComponentName(hypenPath);
|
|
2959
|
-
log12("Component name:", componentName);
|
|
2960
|
-
const modulePath = findModulePath(hypenPath, patterns);
|
|
2961
|
-
log12("Module path:", modulePath);
|
|
2962
|
-
let contents;
|
|
2963
|
-
if (modulePath) {
|
|
2964
|
-
const relativeModulePath = modulePath.replace(/\.ts$/, ".js");
|
|
2965
|
-
contents = `
|
|
2966
|
-
import _module from "${relativeModulePath}";
|
|
2967
|
-
export const module = _module;
|
|
2968
|
-
export const template = ${JSON.stringify(template)};
|
|
2969
|
-
export const name = ${JSON.stringify(componentName)};
|
|
2970
|
-
export default { module: _module, template: ${JSON.stringify(template)}, name: ${JSON.stringify(componentName)} };
|
|
2971
|
-
`;
|
|
2972
|
-
} else {
|
|
2973
|
-
log12("No module found, creating stateless component");
|
|
2974
|
-
contents = `
|
|
2975
|
-
import { app } from "@hypen-space/core";
|
|
2976
|
-
const _module = app.defineState({}).build();
|
|
2977
|
-
export const module = _module;
|
|
2978
|
-
export const template = ${JSON.stringify(template)};
|
|
2979
|
-
export const name = ${JSON.stringify(componentName)};
|
|
2980
|
-
export default { module: _module, template: ${JSON.stringify(template)}, name: ${JSON.stringify(componentName)} };
|
|
2981
|
-
`;
|
|
2982
|
-
}
|
|
2983
|
-
return {
|
|
2984
|
-
contents,
|
|
2985
|
-
loader: "js"
|
|
2986
|
-
};
|
|
2987
|
-
});
|
|
2988
|
-
}
|
|
2989
|
-
};
|
|
2990
|
-
}
|
|
2991
|
-
var defaultHypenPlugin = hypenPlugin();
|
|
2992
|
-
function registerHypenPlugin(options) {
|
|
2993
|
-
Bun.plugin(hypenPlugin(options));
|
|
2994
|
-
}
|
|
2995
|
-
var plugin_default = hypenPlugin;
|
|
2996
|
-
|
|
2997
|
-
// src/components/builtin.ts
|
|
2998
|
-
init_app();
|
|
2999
|
-
init_logger();
|
|
3000
|
-
var log12 = frameworkLoggers.router;
|
|
3001
|
-
var Router = app.defineState({
|
|
3002
|
-
currentPath: "/",
|
|
3003
|
-
matchedRoute: null,
|
|
3004
|
-
routeParams: {}
|
|
3005
|
-
}, { name: "__Router" }).onCreated((state, context) => {
|
|
3006
|
-
if (!context) {
|
|
3007
|
-
log12.error("Requires global context");
|
|
3008
|
-
return;
|
|
3009
|
-
}
|
|
3010
|
-
const router = context.__router;
|
|
3011
|
-
if (!router) {
|
|
3012
|
-
log12.error("Router not found in context");
|
|
3013
|
-
return;
|
|
3014
|
-
}
|
|
3015
|
-
const hypenEngine = context.__hypenEngine;
|
|
3016
|
-
const updateRouteVisibility = async (currentPath) => {
|
|
3017
|
-
const routeElements = document.querySelectorAll('[data-hypen-type="route"]');
|
|
3018
|
-
let matchFound = false;
|
|
3019
|
-
for (let index = 0;index < routeElements.length; index++) {
|
|
3020
|
-
const routeEl = routeElements[index];
|
|
3021
|
-
const htmlEl = routeEl;
|
|
3022
|
-
const routePath = htmlEl.dataset.routePath || "/";
|
|
3023
|
-
const isMatch = routePath === currentPath;
|
|
3024
|
-
htmlEl.style.display = isMatch ? "flex" : "none";
|
|
3025
|
-
if (isMatch) {
|
|
3026
|
-
matchFound = true;
|
|
3027
|
-
const componentName = htmlEl.dataset.routeComponent;
|
|
3028
|
-
const isLazy = htmlEl.dataset.routeLazy === "true";
|
|
3029
|
-
const hasContent = htmlEl.children.length > 0;
|
|
3030
|
-
if (componentName && !hasContent && hypenEngine) {
|
|
3031
|
-
try {
|
|
3032
|
-
await hypenEngine.renderLazyRoute(routePath, componentName, htmlEl);
|
|
3033
|
-
} catch (err) {
|
|
3034
|
-
log12.error(`Failed to render route ${routePath}:`, err);
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
if (!matchFound) {
|
|
3040
|
-
log12.warn(`No route matched path: ${currentPath}. Available routes:`, Array.from(routeElements).map((el) => el.dataset.routePath));
|
|
3041
|
-
}
|
|
3042
|
-
};
|
|
3043
|
-
setTimeout(() => {
|
|
3044
|
-
updateRouteVisibility(state.currentPath);
|
|
3045
|
-
}, 100);
|
|
3046
|
-
router.onNavigate((routeState) => {
|
|
3047
|
-
state.currentPath = routeState.currentPath;
|
|
3048
|
-
state.routeParams = routeState.params;
|
|
3049
|
-
updateRouteVisibility(routeState.currentPath);
|
|
3050
|
-
});
|
|
3051
|
-
}).build();
|
|
3052
|
-
var Route = app.defineState({}, { name: "__Route" }).build();
|
|
3053
|
-
var Link = app.defineState({
|
|
3054
|
-
to: "/",
|
|
3055
|
-
isActive: false
|
|
3056
|
-
}, { name: "__Link" }).onAction("navigate", ({ state, context }) => {
|
|
3057
|
-
const router = context?.__router;
|
|
3058
|
-
if (!router) {
|
|
3059
|
-
log12.error("Link requires router context");
|
|
3060
|
-
return;
|
|
3061
|
-
}
|
|
3062
|
-
const targetPath = state.to;
|
|
3063
|
-
router.push(targetPath);
|
|
3064
|
-
}).onCreated((state, context) => {
|
|
3065
|
-
if (!context)
|
|
3066
|
-
return;
|
|
3067
|
-
const router = context.__router;
|
|
3068
|
-
if (router) {
|
|
3069
|
-
router.onNavigate((routeState) => {
|
|
3070
|
-
state.isActive = routeState.currentPath === state.to;
|
|
3071
|
-
});
|
|
3072
|
-
}
|
|
3073
|
-
}).build();
|
|
3074
|
-
|
|
3075
2180
|
// src/index.ts
|
|
3076
|
-
init_app();
|
|
3077
|
-
|
|
3078
|
-
// src/hypen.ts
|
|
3079
|
-
function createBindingProxy(root) {
|
|
3080
|
-
const handler = {
|
|
3081
|
-
get(_, prop) {
|
|
3082
|
-
if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
|
|
3083
|
-
return () => `\${${root}}`;
|
|
3084
|
-
}
|
|
3085
|
-
if (typeof prop === "symbol") {
|
|
3086
|
-
return;
|
|
3087
|
-
}
|
|
3088
|
-
if (prop === "toJSON") {
|
|
3089
|
-
return () => `\${${root}}`;
|
|
3090
|
-
}
|
|
3091
|
-
return createBindingProxy(`${root}.${prop}`);
|
|
3092
|
-
},
|
|
3093
|
-
has() {
|
|
3094
|
-
return true;
|
|
3095
|
-
},
|
|
3096
|
-
ownKeys() {
|
|
3097
|
-
return [];
|
|
3098
|
-
},
|
|
3099
|
-
getOwnPropertyDescriptor() {
|
|
3100
|
-
return {
|
|
3101
|
-
configurable: true,
|
|
3102
|
-
enumerable: true
|
|
3103
|
-
};
|
|
3104
|
-
}
|
|
3105
|
-
};
|
|
3106
|
-
return new Proxy({}, handler);
|
|
3107
|
-
}
|
|
3108
|
-
var state = createBindingProxy("state");
|
|
3109
|
-
var item = createBindingProxy("item");
|
|
3110
|
-
var index = {
|
|
3111
|
-
[Symbol.toPrimitive]: () => "${index}",
|
|
3112
|
-
toString: () => "${index}",
|
|
3113
|
-
valueOf: () => "${index}",
|
|
3114
|
-
toJSON: () => "${index}"
|
|
3115
|
-
};
|
|
3116
|
-
function hypen(strings, ...expressions) {
|
|
3117
|
-
let result = strings[0];
|
|
3118
|
-
for (let i = 0;i < expressions.length; i++) {
|
|
3119
|
-
const expr = expressions[i];
|
|
3120
|
-
result += String(expr);
|
|
3121
|
-
result += strings[i + 1];
|
|
3122
|
-
}
|
|
3123
|
-
return result.trim();
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
// src/index.ts
|
|
3127
|
-
init_state();
|
|
3128
2181
|
init_result();
|
|
3129
2182
|
init_logger();
|
|
3130
2183
|
export {
|
|
3131
2184
|
withRetry,
|
|
3132
|
-
watchComponents,
|
|
3133
2185
|
usingSync,
|
|
3134
2186
|
using,
|
|
3135
2187
|
unwrapProxy,
|
|
@@ -3139,16 +2191,13 @@ export {
|
|
|
3139
2191
|
state,
|
|
3140
2192
|
setLogLevel,
|
|
3141
2193
|
setDebugMode,
|
|
3142
|
-
serve,
|
|
3143
2194
|
retryResult,
|
|
3144
2195
|
retry,
|
|
3145
|
-
registerHypenPlugin,
|
|
3146
2196
|
match,
|
|
3147
2197
|
mapErr,
|
|
3148
2198
|
map,
|
|
3149
2199
|
logger,
|
|
3150
2200
|
log,
|
|
3151
|
-
loadDiscoveredComponents,
|
|
3152
2201
|
item,
|
|
3153
2202
|
isStateProxy,
|
|
3154
2203
|
isOk,
|
|
@@ -3156,13 +2205,11 @@ export {
|
|
|
3156
2205
|
isDisposable,
|
|
3157
2206
|
isDebugMode,
|
|
3158
2207
|
index,
|
|
3159
|
-
hypenPlugin,
|
|
3160
2208
|
hypen,
|
|
3161
2209
|
hasElementDisposables,
|
|
3162
2210
|
getStateSnapshot,
|
|
3163
2211
|
getLogLevel,
|
|
3164
2212
|
getElementDisposables,
|
|
3165
|
-
generateComponentsCode,
|
|
3166
2213
|
fromTry,
|
|
3167
2214
|
fromPromise,
|
|
3168
2215
|
frameworkLoggers,
|
|
@@ -3175,15 +2222,12 @@ export {
|
|
|
3175
2222
|
disposableListener,
|
|
3176
2223
|
disposableInterval,
|
|
3177
2224
|
disposableAbortController,
|
|
3178
|
-
discoverComponents,
|
|
3179
2225
|
disableLogging,
|
|
3180
|
-
defaultHypenPlugin,
|
|
3181
2226
|
createObservableState,
|
|
3182
2227
|
createLogger,
|
|
3183
2228
|
createEventEmitter,
|
|
3184
2229
|
configureLogger,
|
|
3185
2230
|
compositeDisposable,
|
|
3186
|
-
componentLoader,
|
|
3187
2231
|
batchStateUpdates,
|
|
3188
2232
|
app,
|
|
3189
2233
|
all,
|
|
@@ -3194,7 +2238,6 @@ export {
|
|
|
3194
2238
|
Route,
|
|
3195
2239
|
RetryPresets,
|
|
3196
2240
|
RetryConditions,
|
|
3197
|
-
RemoteServer,
|
|
3198
2241
|
RemoteEngine,
|
|
3199
2242
|
Ok,
|
|
3200
2243
|
Logger,
|
|
@@ -3211,9 +2254,8 @@ export {
|
|
|
3211
2254
|
ConsoleRenderer,
|
|
3212
2255
|
ConnectionError,
|
|
3213
2256
|
ComponentResolver,
|
|
3214
|
-
ComponentLoader,
|
|
3215
2257
|
BaseRenderer,
|
|
3216
2258
|
ActionError
|
|
3217
2259
|
};
|
|
3218
2260
|
|
|
3219
|
-
//# debugId=
|
|
2261
|
+
//# debugId=27843E67245517FE64756E2164756E21
|