@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/index.js CHANGED
@@ -895,7 +895,11 @@ class BaseRenderer {
895
895
 
896
896
  class ConsoleRenderer {
897
897
  applyPatches(patches) {
898
- log3.debug("Patches:", patches);
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/engine.ts
1800
- init_logger();
1801
- import { WasmEngine } from "../wasm-node/hypen_engine.js";
1802
- var log9 = frameworkLoggers.engine;
1803
- function unwrapForWasm(value) {
1804
- if (value === null || typeof value !== "object") {
1805
- return value;
1806
- }
1807
- if (typeof value.__getSnapshot === "function") {
1808
- return value.__getSnapshot();
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
- try {
1811
- return structuredClone(value);
1812
- } catch {
1813
- return JSON.parse(JSON.stringify(value));
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
- class Engine {
1818
- wasmEngine = null;
1819
- initialized = false;
1820
- async init() {
1821
- if (this.initialized)
1822
- return;
1823
- this.wasmEngine = new WasmEngine;
1824
- this.initialized = true;
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
- ensureInitialized() {
1827
- if (!this.wasmEngine) {
1828
- throw new Error("Engine not initialized. Call init() first.");
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
- setRenderCallback(callback) {
1833
- const engine = this.ensureInitialized();
1834
- engine.setRenderCallback((patches) => {
1835
- callback(patches);
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
- setComponentResolver(resolver) {
1839
- const engine = this.ensureInitialized();
1840
- engine.setComponentResolver((componentName, contextPath) => {
1841
- const result = resolver(componentName, contextPath);
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
- renderLazyComponent(source) {
1850
- const engine = this.ensureInitialized();
1851
- engine.renderLazyComponent(source);
1894
+ getSourcePath(source) {
1895
+ return source.type === "local" ? source.path : source.url;
1852
1896
  }
1853
- renderInto(source, parentNodeId, state) {
1854
- const engine = this.ensureInitialized();
1855
- engine.renderInto(source, parentNodeId, unwrapForWasm(state));
1897
+ clearCache() {
1898
+ this.cache.clear();
1856
1899
  }
1857
- notifyStateChange(paths, values) {
1858
- const engine = this.ensureInitialized();
1859
- if (paths.length === 0) {
1860
- return;
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
- engine.updateStateSparse(paths, unwrapForWasm(values));
1863
- log9.debug("State changed (sparse):", paths);
1920
+ return imports;
1864
1921
  }
1865
- notifyStateChangeFull(paths, currentState) {
1866
- const engine = this.ensureInitialized();
1867
- engine.updateState(unwrapForWasm(currentState));
1868
- if (paths.length > 0) {
1869
- log9.debug("State changed (full):", paths);
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
- updateState(statePatch) {
1873
- const engine = this.ensureInitialized();
1874
- engine.updateState(unwrapForWasm(statePatch));
1937
+ const router = context.__router;
1938
+ if (!router) {
1939
+ log9.error("Router not found in context");
1940
+ return;
1875
1941
  }
1876
- dispatchAction(name, payload) {
1877
- const engine = this.ensureInitialized();
1878
- engine.dispatchAction(name, payload ?? null);
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
- onAction(actionName, handler) {
1881
- const engine = this.ensureInitialized();
1882
- engine.onAction(actionName, (action) => {
1883
- Promise.resolve(handler(action)).catch((err) => log9.error("Action handler error:", err));
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
- setModule(name, actions, stateKeys, initialState) {
1887
- const engine = this.ensureInitialized();
1888
- engine.setModule(name, actions, stateKeys, initialState);
1889
- }
1890
- getRevision() {
1891
- const engine = this.ensureInitialized();
1892
- return Number(engine.getRevision());
1893
- }
1894
- clearTree() {
1895
- const engine = this.ensureInitialized();
1896
- engine.clearTree();
1897
- }
1898
- debugParseComponent(source) {
1899
- const engine = this.ensureInitialized();
1900
- return engine.debugParseComponent(source);
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/remote/server.ts
1905
- init_app();
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=C03FB9EC8278859464756E2164756E21
2261
+ //# debugId=27843E67245517FE64756E2164756E21