@hypen-space/core 0.4.36 → 0.4.38

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.
Files changed (80) hide show
  1. package/README.md +13 -14
  2. package/dist/app.js +289 -227
  3. package/dist/app.js.map +5 -5
  4. package/dist/components/builtin.js +289 -227
  5. package/dist/components/builtin.js.map +5 -5
  6. package/dist/context.js +60 -64
  7. package/dist/context.js.map +2 -2
  8. package/dist/datasource.js +80 -0
  9. package/dist/datasource.js.map +10 -0
  10. package/dist/disposable.js +60 -63
  11. package/dist/disposable.js.map +2 -2
  12. package/dist/events.js +60 -63
  13. package/dist/events.js.map +2 -2
  14. package/dist/hypen.js +78 -0
  15. package/dist/hypen.js.map +10 -0
  16. package/dist/index.browser.d.ts +2 -1
  17. package/dist/index.browser.js +303 -244
  18. package/dist/index.browser.js.map +6 -7
  19. package/dist/index.d.ts +0 -3
  20. package/dist/index.js +492 -180
  21. package/dist/index.js.map +6 -6
  22. package/dist/logger.js +60 -64
  23. package/dist/logger.js.map +2 -2
  24. package/dist/remote/client.js +243 -158
  25. package/dist/remote/client.js.map +6 -6
  26. package/dist/remote/index.d.ts +6 -5
  27. package/dist/remote/index.js +241 -1986
  28. package/dist/remote/index.js.map +6 -13
  29. package/dist/remote/session.js +151 -0
  30. package/dist/remote/session.js.map +10 -0
  31. package/dist/renderer.js +60 -63
  32. package/dist/renderer.js.map +2 -2
  33. package/dist/result.js +220 -0
  34. package/dist/result.js.map +10 -0
  35. package/dist/retry.js +329 -0
  36. package/dist/retry.js.map +11 -0
  37. package/dist/router.js +62 -70
  38. package/dist/router.js.map +2 -2
  39. package/dist/state.js +3 -8
  40. package/dist/state.js.map +2 -2
  41. package/package.json +11 -56
  42. package/src/index.browser.ts +5 -4
  43. package/src/index.ts +10 -23
  44. package/src/remote/index.ts +9 -5
  45. package/dist/discovery.d.ts +0 -90
  46. package/dist/discovery.js +0 -1334
  47. package/dist/discovery.js.map +0 -15
  48. package/dist/engine.browser.d.ts +0 -116
  49. package/dist/engine.browser.js +0 -479
  50. package/dist/engine.browser.js.map +0 -12
  51. package/dist/engine.d.ts +0 -107
  52. package/dist/engine.js +0 -543
  53. package/dist/engine.js.map +0 -12
  54. package/dist/loader.d.ts +0 -51
  55. package/dist/loader.js +0 -292
  56. package/dist/loader.js.map +0 -11
  57. package/dist/plugin.d.ts +0 -39
  58. package/dist/plugin.js +0 -685
  59. package/dist/plugin.js.map +0 -12
  60. package/dist/remote/server.d.ts +0 -188
  61. package/dist/remote/server.js +0 -2270
  62. package/dist/remote/server.js.map +0 -19
  63. package/src/discovery.ts +0 -527
  64. package/src/engine.browser.ts +0 -302
  65. package/src/engine.ts +0 -282
  66. package/src/loader.ts +0 -136
  67. package/src/plugin.ts +0 -220
  68. package/src/remote/server.ts +0 -879
  69. package/wasm-browser/README.md +0 -594
  70. package/wasm-browser/hypen_engine.d.ts +0 -389
  71. package/wasm-browser/hypen_engine.js +0 -1070
  72. package/wasm-browser/hypen_engine_bg.wasm +0 -0
  73. package/wasm-browser/hypen_engine_bg.wasm.d.ts +0 -37
  74. package/wasm-browser/package.json +0 -24
  75. package/wasm-node/README.md +0 -594
  76. package/wasm-node/hypen_engine.d.ts +0 -327
  77. package/wasm-node/hypen_engine.js +0 -979
  78. package/wasm-node/hypen_engine_bg.wasm +0 -0
  79. package/wasm-node/hypen_engine_bg.wasm.d.ts +0 -37
  80. package/wasm-node/package.json +0 -22
package/src/discovery.ts DELETED
@@ -1,527 +0,0 @@
1
- /**
2
- * Component Discovery
3
- *
4
- * Automatically discovers Hypen components from the filesystem.
5
- *
6
- * Supports multiple conventions:
7
- * 1. Single-file: ComponentName.ts with .ui(hypen`...`) inline template
8
- * 2. Folder-based: ComponentName/component.ts + ComponentName/component.hypen
9
- * 3. Sibling files: ComponentName.ts + ComponentName.hypen
10
- * 4. Index-based: ComponentName/index.ts + ComponentName/index.hypen
11
- */
12
-
13
- import { existsSync, readdirSync, readFileSync, watch } from "fs";
14
- import { join, basename, resolve, relative } from "path";
15
- import type { HypenModuleDefinition } from "./app.js";
16
- import { frameworkLoggers } from "./logger.js";
17
-
18
- export interface DiscoveredComponent {
19
- name: string;
20
- /** Path to .hypen file (null for single-file components with inline templates) */
21
- hypenPath: string | null;
22
- /** Path to .ts module file */
23
- modulePath: string | null;
24
- /** The UI template (from .hypen file or inline) */
25
- template: string;
26
- /** Whether this component has a module (state/actions) */
27
- hasModule: boolean;
28
- /** Whether this is a single-file component with inline template */
29
- isSingleFile: boolean;
30
- }
31
-
32
- export interface DiscoveryOptions {
33
- /**
34
- * Which naming patterns to look for
35
- * Default: ["single-file", "folder", "sibling", "index"]
36
- *
37
- * - single-file: ComponentName.ts with .ui(hypen`...`) inline template
38
- * - folder: ComponentName/component.ts + ComponentName/component.hypen
39
- * - sibling: ComponentName.ts + ComponentName.hypen
40
- * - index: ComponentName/index.ts + ComponentName/index.hypen
41
- */
42
- patterns?: ("single-file" | "folder" | "sibling" | "index")[];
43
-
44
- /**
45
- * Recursively scan subdirectories
46
- * Default: true
47
- */
48
- recursive?: boolean;
49
-
50
- /**
51
- * Enable debug logging
52
- * Default: false
53
- */
54
- debug?: boolean;
55
- }
56
-
57
- export interface WatchOptions extends DiscoveryOptions {
58
- /**
59
- * Callback when components change
60
- */
61
- onChange?: (components: DiscoveredComponent[]) => void;
62
-
63
- /**
64
- * Callback when a component is added
65
- */
66
- onAdd?: (component: DiscoveredComponent) => void;
67
-
68
- /**
69
- * Callback when a component is removed
70
- */
71
- onRemove?: (name: string) => void;
72
-
73
- /**
74
- * Callback when a component is updated
75
- */
76
- onUpdate?: (component: DiscoveredComponent) => void;
77
- }
78
-
79
- /**
80
- * Remove import statements from Hypen template
81
- */
82
- function removeImports(text: string): string {
83
- return text.replace(
84
- /import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g,
85
- ""
86
- );
87
- }
88
-
89
- /**
90
- * Discover all Hypen components in a directory
91
- */
92
- export async function discoverComponents(
93
- baseDir: string,
94
- options: DiscoveryOptions = {}
95
- ): Promise<DiscoveredComponent[]> {
96
- const {
97
- patterns = ["single-file", "folder", "sibling", "index"],
98
- recursive = true,
99
- debug = false,
100
- } = options;
101
-
102
- const log = debug
103
- ? (...args: unknown[]) => frameworkLoggers.discovery.debug(...args)
104
- : () => {};
105
-
106
- const resolvedDir = resolve(baseDir);
107
- const components: DiscoveredComponent[] = [];
108
- const seen = new Set<string>();
109
-
110
- log("Scanning directory:", resolvedDir);
111
- log("Patterns:", patterns);
112
-
113
- // Helper to add a two-file component (.hypen + .ts)
114
- const addTwoFileComponent = (
115
- name: string,
116
- hypenPath: string,
117
- modulePath: string | null
118
- ) => {
119
- if (seen.has(name)) {
120
- log(`Skipping duplicate: ${name}`);
121
- return;
122
- }
123
-
124
- seen.add(name);
125
- const templateRaw = readFileSync(hypenPath, "utf-8");
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();
129
-
130
- components.push({
131
- name,
132
- hypenPath,
133
- modulePath,
134
- template,
135
- hasModule: modulePath !== null,
136
- isSingleFile: false,
137
- });
138
-
139
- log(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
140
- };
141
-
142
- // Helper to add a single-file component (.ts with inline template)
143
- const addSingleFileComponent = (
144
- name: string,
145
- modulePath: string,
146
- template: string
147
- ) => {
148
- if (seen.has(name)) {
149
- log(`Skipping duplicate: ${name}`);
150
- return;
151
- }
152
-
153
- seen.add(name);
154
- components.push({
155
- name,
156
- hypenPath: null,
157
- modulePath,
158
- template,
159
- hasModule: true,
160
- isSingleFile: true,
161
- });
162
-
163
- log(`Found: ${name} (single-file with inline template)`);
164
- };
165
-
166
- // Scan directory for folder-based components
167
- const scanForFolderComponents = (dir: string) => {
168
- if (!existsSync(dir)) return;
169
-
170
- const entries = readdirSync(dir, { withFileTypes: true });
171
-
172
- for (const entry of entries) {
173
- if (!entry.isDirectory()) continue;
174
-
175
- const folderPath = join(dir, entry.name);
176
- const componentName = entry.name;
177
-
178
- // Check folder-based pattern: Name/component.hypen
179
- if (patterns.includes("folder")) {
180
- const hypenPath = join(folderPath, "component.hypen");
181
- if (existsSync(hypenPath)) {
182
- const modulePath = join(folderPath, "component.ts");
183
- addTwoFileComponent(
184
- componentName,
185
- hypenPath,
186
- existsSync(modulePath) ? modulePath : null
187
- );
188
- continue;
189
- }
190
- }
191
-
192
- // Check index-based pattern: Name/index.hypen
193
- if (patterns.includes("index")) {
194
- const hypenPath = join(folderPath, "index.hypen");
195
- if (existsSync(hypenPath)) {
196
- const modulePath = join(folderPath, "index.ts");
197
- addTwoFileComponent(
198
- componentName,
199
- hypenPath,
200
- existsSync(modulePath) ? modulePath : null
201
- );
202
- continue;
203
- }
204
- }
205
-
206
- // Recursive scan
207
- if (recursive) {
208
- scanForFolderComponents(folderPath);
209
- }
210
- }
211
- };
212
-
213
- // Scan for sibling file components (.ts + .hypen pairs)
214
- const scanForSiblingComponents = (dir: string) => {
215
- if (!existsSync(dir)) return;
216
-
217
- const entries = readdirSync(dir, { withFileTypes: true });
218
-
219
- for (const entry of entries) {
220
- if (entry.isDirectory()) {
221
- if (recursive) {
222
- scanForSiblingComponents(join(dir, entry.name));
223
- }
224
- continue;
225
- }
226
-
227
- if (!entry.name.endsWith(".hypen")) continue;
228
-
229
- const hypenPath = join(dir, entry.name);
230
- const baseName = basename(entry.name, ".hypen");
231
-
232
- // Skip component.hypen and index.hypen (handled by folder patterns)
233
- if (baseName === "component" || baseName === "index") continue;
234
-
235
- const modulePath = join(dir, `${baseName}.ts`);
236
- addTwoFileComponent(
237
- baseName,
238
- hypenPath,
239
- existsSync(modulePath) ? modulePath : null
240
- );
241
- }
242
- };
243
-
244
- // Scan for single-file components (.ts files with inline templates)
245
- const scanForSingleFileComponents = async (dir: string) => {
246
- if (!existsSync(dir)) return;
247
-
248
- const entries = readdirSync(dir, { withFileTypes: true });
249
-
250
- for (const entry of entries) {
251
- if (entry.isDirectory()) {
252
- if (recursive) {
253
- await scanForSingleFileComponents(join(dir, entry.name));
254
- }
255
- continue;
256
- }
257
-
258
- // Only look at .ts files
259
- if (!entry.name.endsWith(".ts")) continue;
260
-
261
- // Skip files that are clearly not components
262
- if (entry.name.startsWith('.') || entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
263
-
264
- // Skip component.ts and index.ts (handled by folder patterns)
265
- const baseName = basename(entry.name, ".ts");
266
- if (baseName === "component" || baseName === "index") continue;
267
-
268
- // Skip if there's a matching .hypen file (handled by sibling pattern)
269
- const hypenPath = join(dir, `${baseName}.hypen`);
270
- if (existsSync(hypenPath)) continue;
271
-
272
- // Check if this looks like a single-file component by looking for .ui( pattern
273
- const modulePath = join(dir, entry.name);
274
- const content = readFileSync(modulePath, "utf-8");
275
-
276
- // Quick heuristic: look for .ui( or .ui(hypen pattern
277
- if (content.includes(".ui(") || content.includes(".ui(hypen")) {
278
- // Try to import and check for template
279
- try {
280
- const moduleExport = await import(modulePath);
281
- const module = moduleExport.default as HypenModuleDefinition<any>;
282
-
283
- if (module && typeof module === "object" && module.template) {
284
- addSingleFileComponent(baseName, modulePath, module.template);
285
- } else if (module && typeof module === "object") {
286
- frameworkLoggers.discovery.warn(`Skipping "${entry.name}": has .ui() pattern but module has no .template property. Did you forget to call .ui(hypen\`...\`)?`);
287
- }
288
- } catch (e) {
289
- // Failed to import, skip this file
290
- log(`Failed to import potential single-file component: ${entry.name}`, e);
291
- }
292
- }
293
- }
294
- };
295
-
296
- // Run scans based on patterns
297
- if (patterns.includes("folder") || patterns.includes("index")) {
298
- scanForFolderComponents(resolvedDir);
299
- }
300
-
301
- if (patterns.includes("sibling")) {
302
- scanForSiblingComponents(resolvedDir);
303
- }
304
-
305
- if (patterns.includes("single-file")) {
306
- await scanForSingleFileComponents(resolvedDir);
307
- }
308
-
309
- log(`Discovered ${components.length} components`);
310
-
311
- return components;
312
- }
313
-
314
- /**
315
- * Load discovered components into a map for use with Hypen
316
- */
317
- export async function loadDiscoveredComponents(
318
- components: DiscoveredComponent[]
319
- ): Promise<
320
- Map<
321
- string,
322
- {
323
- name: string;
324
- module: HypenModuleDefinition<any>;
325
- template: string;
326
- }
327
- >
328
- > {
329
- // Dynamically import app to avoid circular dependencies
330
- const { app } = await import("./app.js");
331
-
332
- const loaded = new Map<
333
- string,
334
- {
335
- name: string;
336
- module: HypenModuleDefinition;
337
- template: string;
338
- }
339
- >();
340
-
341
- for (const component of components) {
342
- let module: HypenModuleDefinition<any>;
343
- let template = component.template;
344
-
345
- if (component.modulePath) {
346
- // Import the TypeScript module
347
- const moduleExport = await import(component.modulePath);
348
- module = moduleExport.default as HypenModuleDefinition<any>;
349
-
350
- // For single-file components, template is already in the module
351
- if (component.isSingleFile && module.template) {
352
- template = module.template;
353
- }
354
- } else {
355
- // Create a stateless module
356
- module = app.defineState({}).build();
357
- }
358
-
359
- loaded.set(component.name, {
360
- name: component.name,
361
- module,
362
- template,
363
- });
364
- }
365
-
366
- return loaded;
367
- }
368
-
369
- /**
370
- * Watch a directory for component changes
371
- */
372
- export function watchComponents(
373
- baseDir: string,
374
- options: WatchOptions = {}
375
- ): { stop: () => void } {
376
- const resolvedDir = resolve(baseDir);
377
- const {
378
- onChange,
379
- onAdd,
380
- onRemove,
381
- onUpdate,
382
- debug = false,
383
- ...discoveryOptions
384
- } = options;
385
-
386
- const log = debug
387
- ? (...args: unknown[]) => frameworkLoggers.discovery.debug("[watch]", ...args)
388
- : () => {};
389
-
390
- let currentComponents = new Map<string, DiscoveredComponent>();
391
- let debounceTimer: ReturnType<typeof setTimeout> | null = null;
392
-
393
- // Initial scan
394
- const initialScan = async () => {
395
- const components = await discoverComponents(resolvedDir, discoveryOptions);
396
- currentComponents = new Map(components.map((c) => [c.name, c]));
397
- onChange?.(components);
398
- };
399
-
400
- // Debounced rescan
401
- const rescan = async () => {
402
- if (debounceTimer) {
403
- clearTimeout(debounceTimer);
404
- }
405
-
406
- debounceTimer = setTimeout(async () => {
407
- log("Rescanning...");
408
- const newComponents = await discoverComponents(
409
- resolvedDir,
410
- discoveryOptions
411
- );
412
- const newMap = new Map(newComponents.map((c) => [c.name, c]));
413
-
414
- // Find added/removed/updated
415
- for (const [name, component] of newMap) {
416
- const existing = currentComponents.get(name);
417
- if (!existing) {
418
- log("Added:", name);
419
- onAdd?.(component);
420
- } else if (
421
- existing.template !== component.template ||
422
- existing.modulePath !== component.modulePath
423
- ) {
424
- log("Updated:", name);
425
- onUpdate?.(component);
426
- }
427
- }
428
-
429
- for (const name of currentComponents.keys()) {
430
- if (!newMap.has(name)) {
431
- log("Removed:", name);
432
- onRemove?.(name);
433
- }
434
- }
435
-
436
- currentComponents = newMap;
437
- onChange?.(newComponents);
438
- }, 100);
439
- };
440
-
441
- // Start watching
442
- const watcher = watch(
443
- resolvedDir,
444
- { recursive: true },
445
- (event, filename) => {
446
- if (!filename) return;
447
- if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
448
- log("File changed:", filename);
449
- rescan();
450
- }
451
- }
452
- );
453
-
454
- // Initial scan
455
- initialScan();
456
-
457
- return {
458
- stop: () => {
459
- watcher.close();
460
- if (debounceTimer) {
461
- clearTimeout(debounceTimer);
462
- }
463
- },
464
- };
465
- }
466
-
467
- /**
468
- * Generate a components object from discovered components
469
- * Useful for static imports / code generation
470
- */
471
- export async function generateComponentsCode(
472
- baseDir: string,
473
- options: DiscoveryOptions & { outputDir?: string } = {}
474
- ): Promise<string> {
475
- const components = await discoverComponents(baseDir, options);
476
- const resolvedDir = resolve(baseDir);
477
- const outputBase = options.outputDir ? resolve(options.outputDir) : resolvedDir;
478
-
479
- let code = `/**
480
- * Auto-generated component imports
481
- * Generated by @hypen-space/core discovery
482
- */
483
-
484
- `;
485
-
486
- for (const component of components) {
487
- let importPath: string | null = null;
488
- if (component.modulePath) {
489
- const rel = relative(outputBase, component.modulePath)
490
- .replace(/\.ts$/, ".js");
491
- importPath = rel.startsWith(".") ? rel : "./" + rel;
492
- }
493
-
494
- if (importPath) {
495
- code += `import ${component.name}Module from "${importPath}";\n`;
496
- }
497
- }
498
-
499
- code += `\nimport { app } from "@hypen-space/core";\n\n`;
500
-
501
- for (const component of components) {
502
- if (component.isSingleFile) {
503
- // Single-file component: template is in the module itself
504
- code += `export const ${component.name} = {
505
- module: ${component.name}Module,
506
- template: ${component.name}Module.template,
507
- };\n\n`;
508
- } else if (component.hasModule) {
509
- // Two-file component with module
510
- const templateJson = JSON.stringify(component.template);
511
- code += `export const ${component.name} = {
512
- module: ${component.name}Module,
513
- template: ${templateJson},
514
- };\n\n`;
515
- } else {
516
- // Stateless two-file component
517
- const templateJson = JSON.stringify(component.template);
518
- code += `const ${component.name}Module = app.defineState({}).build();
519
- export const ${component.name} = {
520
- module: ${component.name}Module,
521
- template: ${templateJson},
522
- };\n\n`;
523
- }
524
- }
525
-
526
- return code;
527
- }