@hypen-space/core 0.3.12 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +7 -3
- package/dist/app.js.map +3 -3
- package/dist/components/builtin.js +7 -3
- package/dist/components/builtin.js.map +3 -3
- package/dist/discovery.js +8 -7
- package/dist/discovery.js.map +4 -4
- package/dist/engine.browser.js.map +2 -2
- package/dist/engine.js +3 -7
- package/dist/engine.js.map +3 -3
- package/dist/index.browser.js +242 -371
- package/dist/index.browser.js.map +8 -9
- package/dist/index.js +546 -656
- package/dist/index.js.map +12 -13
- package/dist/remote/client.js.map +1 -1
- package/dist/remote/index.js +336 -336
- package/dist/remote/index.js.map +8 -8
- package/dist/remote/server.js +219 -219
- package/dist/remote/server.js.map +7 -7
- package/dist/renderer.js.map +1 -1
- package/dist/resolver.js +31 -4
- package/dist/resolver.js.map +3 -3
- package/package.json +13 -1
- package/src/app.ts +14 -2
- package/src/discovery.ts +15 -12
- package/src/engine.browser.ts +4 -32
- package/src/engine.ts +8 -44
- package/src/index.browser.ts +6 -4
- package/src/index.ts +22 -30
- package/src/managed-router.ts +195 -0
- package/src/module-registry.ts +76 -0
- package/src/remote/client.ts +1 -1
- package/src/remote/index.ts +2 -2
- package/src/remote/server.ts +1 -1
- package/src/remote/types.ts +1 -1
- package/src/renderer.ts +6 -2
- package/src/resolver.ts +56 -9
- package/src/types.ts +38 -0
- package/wasm-browser/README.md +7 -1
- package/wasm-browser/hypen_engine.d.ts +1 -0
- package/wasm-browser/hypen_engine.js +1 -0
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +1 -1
- package/wasm-node/README.md +7 -1
- package/wasm-node/hypen_engine.d.ts +1 -0
- package/wasm-node/hypen_engine.js +1 -0
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +1 -1
package/dist/resolver.js
CHANGED
|
@@ -31,14 +31,32 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
31
31
|
class ComponentResolver {
|
|
32
32
|
cache = new Map;
|
|
33
33
|
options;
|
|
34
|
+
moduleRegistry;
|
|
34
35
|
constructor(options = {}) {
|
|
35
36
|
this.options = {
|
|
36
37
|
baseDir: options.baseDir || process.cwd(),
|
|
37
38
|
cache: options.cache ?? true,
|
|
38
|
-
customFetch: options.customFetch || this.defaultFetch.bind(this)
|
|
39
|
+
customFetch: options.customFetch || this.defaultFetch.bind(this),
|
|
40
|
+
moduleRegistry: options.moduleRegistry
|
|
39
41
|
};
|
|
42
|
+
this.moduleRegistry = options.moduleRegistry;
|
|
40
43
|
}
|
|
41
44
|
async resolve(importStmt) {
|
|
45
|
+
if (this.moduleRegistry) {
|
|
46
|
+
const names = importStmt.clause.type === "named" ? importStmt.clause.names : [importStmt.clause.name];
|
|
47
|
+
const allFound = names.every((name) => this.moduleRegistry.has(name));
|
|
48
|
+
if (allFound) {
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
const def = this.moduleRegistry.get(name);
|
|
52
|
+
result[name] = {
|
|
53
|
+
module: def,
|
|
54
|
+
template: def?.template || ""
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
42
60
|
const sourcePath = this.getSourcePath(importStmt.source);
|
|
43
61
|
if (this.options.cache && this.cache.has(sourcePath)) {
|
|
44
62
|
const cached = this.cache.get(sourcePath);
|
|
@@ -56,8 +74,17 @@ class ComponentResolver {
|
|
|
56
74
|
return this.extractComponents(importStmt.clause, component);
|
|
57
75
|
}
|
|
58
76
|
async resolveLocal(path) {
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
const { resolve, join } = await import("path");
|
|
78
|
+
const { readFile } = await import("fs/promises");
|
|
79
|
+
const basePath = resolve(this.options.baseDir, path);
|
|
80
|
+
const hypenPath = basePath.endsWith(".hypen") ? basePath : `${basePath}.hypen`;
|
|
81
|
+
const template = await readFile(hypenPath, "utf-8");
|
|
82
|
+
const modulePath = hypenPath.replace(/\.hypen$/, ".ts");
|
|
83
|
+
let module = {};
|
|
84
|
+
try {
|
|
85
|
+
module = await import(modulePath);
|
|
86
|
+
} catch {}
|
|
87
|
+
return { module, template };
|
|
61
88
|
}
|
|
62
89
|
async resolveUrl(url) {
|
|
63
90
|
try {
|
|
@@ -124,4 +151,4 @@ export {
|
|
|
124
151
|
ComponentResolver
|
|
125
152
|
};
|
|
126
153
|
|
|
127
|
-
//# debugId=
|
|
154
|
+
//# debugId=20EC11627252563C64756E2164756E21
|
package/dist/resolver.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/resolver.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Component Resolver\n * Resolves component imports from local paths or web URLs\n */\n\nexport interface ImportStatement {\n clause: ImportClause;\n source: ImportSource;\n}\n\nexport type ImportClause =\n | { type: \"named\"; names: string[] }\n | { type: \"default\"; name: string };\n\nexport type ImportSource =\n | { type: \"local\"; path: string }\n | { type: \"url\"; url: string };\n\nexport interface ComponentDefinition {\n module: any;\n template: string;\n}\n\nexport interface ResolverOptions {\n /**\n * Base directory for resolving relative local paths\n * Defaults to current working directory\n */\n baseDir?: string;\n\n /**\n * Cache resolved components to avoid re-fetching\n * Defaults to true\n */\n cache?: boolean;\n\n /**\n * Custom fetch function for URL imports\n * Useful for adding authentication, custom headers, etc.\n */\n customFetch?: (url: string) => Promise<string>;\n}\n\n/**\n * Component Resolver\n * Resolves and loads components from local files or remote URLs\n */\nexport class ComponentResolver {\n private cache = new Map<string, ComponentDefinition>();\n private options: Required<ResolverOptions>;\n\n constructor(options: ResolverOptions = {}) {\n this.options = {\n baseDir: options.baseDir || process.cwd(),\n cache: options.cache ?? true,\n customFetch: options.customFetch || this.defaultFetch.bind(this),\n };\n }\n\n /**\n * Resolve a component from an import statement
|
|
5
|
+
"/**\n * Component Resolver\n * Resolves component imports from local paths or web URLs\n */\n\nexport interface ImportStatement {\n clause: ImportClause;\n source: ImportSource;\n}\n\nexport type ImportClause =\n | { type: \"named\"; names: string[] }\n | { type: \"default\"; name: string };\n\nexport type ImportSource =\n | { type: \"local\"; path: string }\n | { type: \"url\"; url: string };\n\nexport interface ComponentDefinition {\n module: any;\n template: string;\n}\n\nexport interface ResolverOptions {\n /**\n * Base directory for resolving relative local paths\n * Defaults to current working directory\n */\n baseDir?: string;\n\n /**\n * Cache resolved components to avoid re-fetching\n * Defaults to true\n */\n cache?: boolean;\n\n /**\n * Custom fetch function for URL imports\n * Useful for adding authentication, custom headers, etc.\n */\n customFetch?: (url: string) => Promise<string>;\n\n /**\n * Module registry for looking up pre-registered module definitions.\n * When set, the resolver will check the registry before doing file I/O.\n */\n moduleRegistry?: { has: (name: string) => boolean; get: (name: string) => any };\n}\n\n/**\n * Component Resolver\n * Resolves and loads components from local files or remote URLs\n */\nexport class ComponentResolver {\n private cache = new Map<string, ComponentDefinition>();\n private options: Required<Pick<ResolverOptions, 'baseDir' | 'cache' | 'customFetch'>> & Pick<ResolverOptions, 'moduleRegistry'>;\n\n private moduleRegistry?: { has: (name: string) => boolean; get: (name: string) => any };\n\n constructor(options: ResolverOptions = {}) {\n this.options = {\n baseDir: options.baseDir || process.cwd(),\n cache: options.cache ?? true,\n customFetch: options.customFetch || this.defaultFetch.bind(this),\n moduleRegistry: options.moduleRegistry,\n };\n this.moduleRegistry = options.moduleRegistry;\n }\n\n /**\n * Resolve a component from an import statement.\n * Checks the module registry first (if available), then falls back to file I/O.\n */\n async resolve(\n importStmt: ImportStatement\n ): Promise<Record<string, ComponentDefinition>> {\n // Check module registry first — if a component is pre-registered,\n // use its template directly (it will be mounted as a full module)\n if (this.moduleRegistry) {\n const names = importStmt.clause.type === \"named\"\n ? importStmt.clause.names\n : [importStmt.clause.name];\n\n const allFound = names.every(name => this.moduleRegistry!.has(name));\n if (allFound) {\n const result: Record<string, ComponentDefinition> = {};\n for (const name of names) {\n const def = this.moduleRegistry!.get(name);\n result[name] = {\n module: def,\n template: def?.template || \"\",\n };\n }\n return result;\n }\n }\n\n const sourcePath = this.getSourcePath(importStmt.source);\n\n // Check cache first\n if (this.options.cache && this.cache.has(sourcePath)) {\n const cached = this.cache.get(sourcePath)!;\n return this.extractComponents(importStmt.clause, cached);\n }\n\n // Load the component\n let component: ComponentDefinition;\n if (importStmt.source.type === \"local\") {\n component = await this.resolveLocal(importStmt.source.path);\n } else {\n component = await this.resolveUrl(importStmt.source.url);\n }\n\n // Cache it\n if (this.options.cache) {\n this.cache.set(sourcePath, component);\n }\n\n return this.extractComponents(importStmt.clause, component);\n }\n\n /**\n * Resolve a component from a local file path\n */\n private async resolveLocal(path: string): Promise<ComponentDefinition> {\n const { resolve, join } = await import(\"path\");\n const { readFile } = await import(\"fs/promises\");\n\n const basePath = resolve(this.options.baseDir, path);\n\n // Try .hypen extension\n const hypenPath = basePath.endsWith(\".hypen\")\n ? basePath\n : `${basePath}.hypen`;\n\n const template = await readFile(hypenPath, \"utf-8\");\n\n // Try loading sibling .ts module (stateless components have no module)\n const modulePath = hypenPath.replace(/\\.hypen$/, \".ts\");\n let module = {};\n try {\n module = await import(modulePath);\n } catch {\n // Stateless component — no module file\n }\n\n return { module, template };\n }\n\n /**\n * Resolve a component from a URL\n */\n private async resolveUrl(url: string): Promise<ComponentDefinition> {\n try {\n const response = await this.options.customFetch(url);\n const data = JSON.parse(response);\n\n // Expected format: { module: {...}, template: \"...\" }\n if (!data.module || !data.template) {\n throw new Error(\n `Invalid component format from ${url}. Expected { module, template }`\n );\n }\n\n return data;\n } catch (error) {\n throw new Error(\n `Failed to resolve component from ${url}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Default fetch implementation\n */\n private async defaultFetch(url: string): Promise<string> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return response.text();\n }\n\n /**\n * Extract the requested components based on the import clause\n */\n private extractComponents(\n clause: ImportClause,\n component: ComponentDefinition\n ): Record<string, ComponentDefinition> {\n if (clause.type === \"default\") {\n return {\n [clause.name]: component,\n };\n } else {\n // Named imports - for now, we only support single exports\n // In the future, we could support exporting multiple components\n // from a single file\n const result: Record<string, ComponentDefinition> = {};\n for (const name of clause.names) {\n result[name] = component;\n }\n return result;\n }\n }\n\n /**\n * Get the source path as a string (for caching)\n */\n private getSourcePath(source: ImportSource): string {\n return source.type === \"local\" ? source.path : source.url;\n }\n\n /**\n * Clear the component cache\n */\n clearCache(): void {\n this.cache.clear();\n }\n\n /**\n * Parse import statements from Hypen DSL text\n * This is a simple parser that extracts import statements\n */\n static parseImports(text: string): ImportStatement[] {\n const imports: ImportStatement[] = [];\n const importRegex =\n /import\\s+(?:(\\{[^}]*\\})|(\\w+))\\s+from\\s+[\"']([^\"']+)[\"']/g;\n\n let match;\n while ((match = importRegex.exec(text)) !== null) {\n const [, namedImports, defaultImport, source] = match;\n\n if (!source) continue;\n\n let clause: ImportClause;\n if (namedImports) {\n // Named imports: { Button, Card }\n const names = namedImports\n .slice(1, -1) // Remove { and }\n .split(\",\")\n .map((n) => n.trim())\n .filter((n) => n.length > 0);\n clause = { type: \"named\", names };\n } else if (defaultImport) {\n // Default import: HomePage\n clause = { type: \"default\", name: defaultImport };\n } else {\n continue;\n }\n\n // Determine if source is URL or local path\n const sourceObj: ImportSource = source.startsWith(\"http://\") ||\n source.startsWith(\"https://\")\n ? { type: \"url\", url: source }\n : { type: \"local\", path: source };\n\n imports.push({ clause, source: sourceObj });\n }\n\n return imports;\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "20EC11627252563C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypen-space/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Hypen core engine - Platform-agnostic reactive UI runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -129,6 +129,18 @@
|
|
|
129
129
|
"bun": "./src/disposable.ts",
|
|
130
130
|
"import": "./dist/disposable.js",
|
|
131
131
|
"default": "./dist/disposable.js"
|
|
132
|
+
},
|
|
133
|
+
"./types": {
|
|
134
|
+
"types": "./dist/types.d.ts",
|
|
135
|
+
"bun": "./src/types.ts",
|
|
136
|
+
"import": "./dist/types.js",
|
|
137
|
+
"default": "./dist/types.js"
|
|
138
|
+
},
|
|
139
|
+
"./logger": {
|
|
140
|
+
"types": "./dist/logger.d.ts",
|
|
141
|
+
"bun": "./src/logger.ts",
|
|
142
|
+
"import": "./dist/logger.js",
|
|
143
|
+
"default": "./dist/logger.js"
|
|
132
144
|
}
|
|
133
145
|
},
|
|
134
146
|
"files": [
|
package/src/app.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Implements the stateful module system from RFC-0001
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { Action } from "./
|
|
6
|
+
import type { Action } from "./types.js";
|
|
7
7
|
import type { Session } from "./remote/types.js";
|
|
8
8
|
import { type Result, Ok, Err, fromPromise, ActionError, HypenError } from "./result.js";
|
|
9
9
|
|
|
@@ -383,7 +383,11 @@ export class HypenModuleInstance<T extends object = any> {
|
|
|
383
383
|
this.routerContext = routerContext;
|
|
384
384
|
this.globalContext = globalContext;
|
|
385
385
|
|
|
386
|
+
// Named state prefix — all lowercase per convention
|
|
387
|
+
const statePrefix = (definition.name || "").toLowerCase();
|
|
388
|
+
|
|
386
389
|
// Create observable state that sends only changed paths and values to engine
|
|
390
|
+
// When a prefix is set, all paths are namespaced (e.g., "homepage.count")
|
|
387
391
|
this.state = createObservableState<T>(definition.initialState as T & object, {
|
|
388
392
|
onChange: (change: StateChange) => {
|
|
389
393
|
// Send only the changed paths and their new values (not the whole state)
|
|
@@ -391,14 +395,22 @@ export class HypenModuleInstance<T extends object = any> {
|
|
|
391
395
|
// Notify all registered callbacks
|
|
392
396
|
this.stateChangeCallbacks.forEach(cb => cb());
|
|
393
397
|
},
|
|
398
|
+
pathPrefix: statePrefix || undefined,
|
|
394
399
|
});
|
|
395
400
|
|
|
396
401
|
// Register with engine (initial state registration)
|
|
402
|
+
// When prefixed, nest the state under the module name so the engine sees
|
|
403
|
+
// e.g., { homepage: { count: 0 } } instead of { count: 0 }
|
|
404
|
+
const snapshot = getStateSnapshot(this.state);
|
|
405
|
+
const prefixedState = statePrefix
|
|
406
|
+
? { [statePrefix]: snapshot }
|
|
407
|
+
: snapshot;
|
|
408
|
+
|
|
397
409
|
this.engine.setModule(
|
|
398
410
|
definition.name || "AnonymousModule",
|
|
399
411
|
definition.actions,
|
|
400
412
|
definition.stateKeys,
|
|
401
|
-
|
|
413
|
+
prefixedState
|
|
402
414
|
);
|
|
403
415
|
|
|
404
416
|
// Register action handlers with flexible parameter support
|
package/src/discovery.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { existsSync, readdirSync, readFileSync, watch } from "fs";
|
|
14
|
-
import { join, basename, resolve } from "path";
|
|
14
|
+
import { join, basename, resolve, relative } from "path";
|
|
15
15
|
import type { HypenModuleDefinition } from "./app.js";
|
|
16
16
|
import { frameworkLoggers } from "./logger.js";
|
|
17
17
|
|
|
@@ -123,7 +123,9 @@ export async function discoverComponents(
|
|
|
123
123
|
|
|
124
124
|
seen.add(name);
|
|
125
125
|
const templateRaw = readFileSync(hypenPath, "utf-8");
|
|
126
|
-
|
|
126
|
+
// Preserve import statements in templates — the engine now processes them
|
|
127
|
+
// via parse_document() and resolves imports through the component resolver
|
|
128
|
+
const template = templateRaw.trim();
|
|
127
129
|
|
|
128
130
|
components.push({
|
|
129
131
|
name,
|
|
@@ -466,10 +468,11 @@ export function watchComponents(
|
|
|
466
468
|
*/
|
|
467
469
|
export async function generateComponentsCode(
|
|
468
470
|
baseDir: string,
|
|
469
|
-
options: DiscoveryOptions = {}
|
|
471
|
+
options: DiscoveryOptions & { outputDir?: string } = {}
|
|
470
472
|
): Promise<string> {
|
|
471
473
|
const components = await discoverComponents(baseDir, options);
|
|
472
474
|
const resolvedDir = resolve(baseDir);
|
|
475
|
+
const outputBase = options.outputDir ? resolve(options.outputDir) : resolvedDir;
|
|
473
476
|
|
|
474
477
|
let code = `/**
|
|
475
478
|
* Auto-generated component imports
|
|
@@ -479,15 +482,15 @@ export async function generateComponentsCode(
|
|
|
479
482
|
`;
|
|
480
483
|
|
|
481
484
|
for (const component of components) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (
|
|
490
|
-
code += `import ${component.name}Module from "${
|
|
485
|
+
let importPath: string | null = null;
|
|
486
|
+
if (component.modulePath) {
|
|
487
|
+
const rel = relative(outputBase, component.modulePath)
|
|
488
|
+
.replace(/\.ts$/, ".js");
|
|
489
|
+
importPath = rel.startsWith(".") ? rel : "./" + rel;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (importPath) {
|
|
493
|
+
code += `import ${component.name}Module from "${importPath}";\n`;
|
|
491
494
|
}
|
|
492
495
|
}
|
|
493
496
|
|
package/src/engine.browser.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { frameworkLoggers } from "./logger.js";
|
|
7
|
+
import type { Patch, Action, RenderCallback, ActionHandler, ResolvedComponent, ComponentResolver } from "./types.js";
|
|
8
|
+
|
|
9
|
+
// Re-export types so existing consumers of "./engine.browser.js" still work
|
|
10
|
+
export type { Patch, Action, RenderCallback, ActionHandler, ResolvedComponent, ComponentResolver } from "./types.js";
|
|
7
11
|
|
|
8
12
|
const log = frameworkLoggers.engine;
|
|
9
13
|
|
|
@@ -12,38 +16,6 @@ const log = frameworkLoggers.engine;
|
|
|
12
16
|
let wasmInit: ((path?: string) => Promise<void>) | null = null;
|
|
13
17
|
let WasmEngineClass: any = null;
|
|
14
18
|
|
|
15
|
-
export type Patch = {
|
|
16
|
-
type: "create" | "setProp" | "setText" | "insert" | "move" | "remove" | "attachEvent" | "detachEvent";
|
|
17
|
-
id?: string;
|
|
18
|
-
elementType?: string;
|
|
19
|
-
props?: Record<string, any>;
|
|
20
|
-
name?: string;
|
|
21
|
-
value?: any;
|
|
22
|
-
text?: string;
|
|
23
|
-
parentId?: string;
|
|
24
|
-
beforeId?: string;
|
|
25
|
-
eventName?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type Action = {
|
|
29
|
-
name: string;
|
|
30
|
-
payload?: any;
|
|
31
|
-
sender?: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type RenderCallback = (patches: Patch[]) => void;
|
|
35
|
-
export type ActionHandler = (action: Action) => void | Promise<void>;
|
|
36
|
-
|
|
37
|
-
export type ResolvedComponent = {
|
|
38
|
-
source: string;
|
|
39
|
-
path: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export type ComponentResolver = (
|
|
43
|
-
componentName: string,
|
|
44
|
-
contextPath: string | null
|
|
45
|
-
) => ResolvedComponent | null;
|
|
46
|
-
|
|
47
19
|
export interface EngineInitOptions {
|
|
48
20
|
/**
|
|
49
21
|
* URL to the WASM binary file.
|
package/src/engine.ts
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Node.js / Bundler target
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// WASM module
|
|
7
|
-
import
|
|
8
|
-
type WasmEngine = WasmEngineType;
|
|
9
|
-
let WasmEngineClass: (new () => WasmEngine) | null = null;
|
|
10
|
-
|
|
6
|
+
// WASM module types
|
|
7
|
+
import { WasmEngine } from "../wasm-node/hypen_engine.js";
|
|
11
8
|
import { frameworkLoggers } from "./logger.js";
|
|
9
|
+
import type { Patch, Action, RenderCallback, ActionHandler, ResolvedComponent, ComponentResolver } from "./types.js";
|
|
10
|
+
|
|
11
|
+
// Re-export types so existing consumers of "./engine.js" still work
|
|
12
|
+
export type { Patch, Action, RenderCallback, ActionHandler, ResolvedComponent, ComponentResolver } from "./types.js";
|
|
12
13
|
|
|
13
14
|
const log = frameworkLoggers.engine;
|
|
14
15
|
|
|
@@ -37,38 +38,6 @@ function unwrapForWasm<T>(value: T): T {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
export type Patch = {
|
|
41
|
-
type: "create" | "setProp" | "setText" | "insert" | "move" | "remove" | "attachEvent" | "detachEvent";
|
|
42
|
-
id?: string;
|
|
43
|
-
elementType?: string;
|
|
44
|
-
props?: Record<string, any>;
|
|
45
|
-
name?: string;
|
|
46
|
-
value?: any;
|
|
47
|
-
text?: string;
|
|
48
|
-
parentId?: string;
|
|
49
|
-
beforeId?: string;
|
|
50
|
-
eventName?: string;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type Action = {
|
|
54
|
-
name: string;
|
|
55
|
-
payload?: any;
|
|
56
|
-
sender?: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type RenderCallback = (patches: Patch[]) => void;
|
|
60
|
-
export type ActionHandler = (action: Action) => void | Promise<void>;
|
|
61
|
-
|
|
62
|
-
export type ResolvedComponent = {
|
|
63
|
-
source: string;
|
|
64
|
-
path: string;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export type ComponentResolver = (
|
|
68
|
-
componentName: string,
|
|
69
|
-
contextPath: string | null
|
|
70
|
-
) => ResolvedComponent | null;
|
|
71
|
-
|
|
72
41
|
/**
|
|
73
42
|
* Engine wraps the WASM engine and provides a TypeScript-friendly API
|
|
74
43
|
*/
|
|
@@ -82,13 +51,8 @@ export class Engine {
|
|
|
82
51
|
async init(): Promise<void> {
|
|
83
52
|
if (this.initialized) return;
|
|
84
53
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
const wasm = await import("../wasm-node/hypen_engine.js");
|
|
88
|
-
WasmEngineClass = wasm.WasmEngine;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
this.wasmEngine = new WasmEngineClass();
|
|
54
|
+
// For bundler target, WASM is auto-initialized
|
|
55
|
+
this.wasmEngine = new WasmEngine();
|
|
92
56
|
this.initialized = true;
|
|
93
57
|
}
|
|
94
58
|
|
package/src/index.browser.ts
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// ============================================================================
|
|
8
|
-
//
|
|
8
|
+
// CORE TYPES (WASM-free)
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
|
-
export { Engine } from "./engine.browser.js";
|
|
12
11
|
export type {
|
|
13
12
|
Patch,
|
|
14
13
|
Action,
|
|
@@ -16,8 +15,11 @@ export type {
|
|
|
16
15
|
ActionHandler as EngineActionHandler,
|
|
17
16
|
ResolvedComponent,
|
|
18
17
|
ComponentResolver,
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
} from "./types.js";
|
|
19
|
+
|
|
20
|
+
// NOTE: Engine (BrowserEngine) is NOT exported from the barrel.
|
|
21
|
+
// Import it explicitly from its subpath to avoid pulling in WASM:
|
|
22
|
+
// import { Engine } from "@hypen-space/core/engine/browser"
|
|
21
23
|
|
|
22
24
|
// ============================================================================
|
|
23
25
|
// APP / MODULE SYSTEM
|
package/src/index.ts
CHANGED
|
@@ -42,10 +42,9 @@
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
// ============================================================================
|
|
45
|
-
//
|
|
45
|
+
// CORE TYPES (WASM-free)
|
|
46
46
|
// ============================================================================
|
|
47
47
|
|
|
48
|
-
export { Engine } from "./engine.js";
|
|
49
48
|
export type {
|
|
50
49
|
Patch,
|
|
51
50
|
Action,
|
|
@@ -53,11 +52,12 @@ export type {
|
|
|
53
52
|
ActionHandler as EngineActionHandler,
|
|
54
53
|
ResolvedComponent,
|
|
55
54
|
ComponentResolver as EngineComponentResolver,
|
|
56
|
-
} from "./
|
|
55
|
+
} from "./types.js";
|
|
57
56
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
// NOTE: Engine and BrowserEngine are NOT exported from the barrel.
|
|
58
|
+
// Import them explicitly from their subpaths to avoid pulling in WASM:
|
|
59
|
+
// import { Engine } from "@hypen-space/core/engine"
|
|
60
|
+
// import { Engine } from "@hypen-space/core/engine/browser"
|
|
61
61
|
|
|
62
62
|
// ============================================================================
|
|
63
63
|
// APP / MODULE SYSTEM
|
|
@@ -149,7 +149,7 @@ export type { ModuleReference } from "./context.js";
|
|
|
149
149
|
// REMOTE UI
|
|
150
150
|
// ============================================================================
|
|
151
151
|
|
|
152
|
-
// Client
|
|
152
|
+
// Client (browser-safe)
|
|
153
153
|
export { RemoteEngine } from "./remote/client.js";
|
|
154
154
|
export type {
|
|
155
155
|
RemoteConnectionState,
|
|
@@ -158,8 +158,9 @@ export type {
|
|
|
158
158
|
SessionInfo,
|
|
159
159
|
} from "./remote/client.js";
|
|
160
160
|
|
|
161
|
-
//
|
|
162
|
-
|
|
161
|
+
// NOTE: RemoteServer and serve are NOT exported from the barrel because they
|
|
162
|
+
// import the Node.js WASM engine. Import them from the subpath instead:
|
|
163
|
+
// import { RemoteServer, serve } from "@hypen-space/core/remote/server"
|
|
163
164
|
|
|
164
165
|
// Session Management
|
|
165
166
|
export { SessionManager } from "./remote/session.js";
|
|
@@ -182,14 +183,7 @@ export type {
|
|
|
182
183
|
} from "./remote/types.js";
|
|
183
184
|
|
|
184
185
|
// ============================================================================
|
|
185
|
-
// COMPONENT
|
|
186
|
-
// ============================================================================
|
|
187
|
-
|
|
188
|
-
export { ComponentLoader, componentLoader } from "./loader.js";
|
|
189
|
-
export type { ComponentDefinition } from "./loader.js";
|
|
190
|
-
|
|
191
|
-
// ============================================================================
|
|
192
|
-
// COMPONENT RESOLVER
|
|
186
|
+
// COMPONENT RESOLVER (browser-safe, no fs dependency)
|
|
193
187
|
// ============================================================================
|
|
194
188
|
|
|
195
189
|
export { ComponentResolver } from "./resolver.js";
|
|
@@ -202,26 +196,24 @@ export type {
|
|
|
202
196
|
} from "./resolver.js";
|
|
203
197
|
|
|
204
198
|
// ============================================================================
|
|
205
|
-
//
|
|
199
|
+
// NODE-ONLY MODULES (NOT exported from barrel — use subpath imports)
|
|
206
200
|
// ============================================================================
|
|
201
|
+
//
|
|
202
|
+
// These modules use Node.js APIs (fs.readFileSync) and must NOT be in the
|
|
203
|
+
// browser-safe barrel. Import them from their subpaths instead:
|
|
204
|
+
//
|
|
205
|
+
// import { ComponentLoader, componentLoader } from "@hypen-space/core/loader"
|
|
206
|
+
// import { discoverComponents, ... } from "@hypen-space/core/discovery"
|
|
207
|
+
// import { hypenPlugin, ... } from "@hypen-space/core/plugin"
|
|
208
|
+
//
|
|
207
209
|
|
|
208
|
-
export
|
|
209
|
-
|
|
210
|
-
loadDiscoveredComponents,
|
|
211
|
-
watchComponents,
|
|
212
|
-
generateComponentsCode,
|
|
213
|
-
} from "./discovery.js";
|
|
210
|
+
// Re-export types only (types are always safe)
|
|
211
|
+
export type { ComponentDefinition } from "./loader.js";
|
|
214
212
|
export type {
|
|
215
213
|
DiscoveredComponent,
|
|
216
214
|
DiscoveryOptions,
|
|
217
215
|
WatchOptions,
|
|
218
216
|
} from "./discovery.js";
|
|
219
|
-
|
|
220
|
-
// ============================================================================
|
|
221
|
-
// PLUGIN
|
|
222
|
-
// ============================================================================
|
|
223
|
-
|
|
224
|
-
export { hypenPlugin, registerHypenPlugin, defaultHypenPlugin } from "./plugin.js";
|
|
225
217
|
export type { HypenPluginOptions } from "./plugin.js";
|
|
226
218
|
|
|
227
219
|
// ============================================================================
|